diff --git a/experimental/packages/exporter-logs-otlp-http/src/platform/browser/OTLPLogExporter.ts b/experimental/packages/exporter-logs-otlp-http/src/platform/browser/OTLPLogExporter.ts index ef837614e2f..a7ecbbac95d 100644 --- a/experimental/packages/exporter-logs-otlp-http/src/platform/browser/OTLPLogExporter.ts +++ b/experimental/packages/exporter-logs-otlp-http/src/platform/browser/OTLPLogExporter.ts @@ -48,7 +48,7 @@ export class OTLPLogExporter } convert(logRecords: ReadableLogRecord[]): IExportLogsServiceRequest { - return createExportLogsServiceRequest(logRecords); + return createExportLogsServiceRequest(logRecords, true); } getDefaultUrl(config: OTLPExporterConfigBase): string { diff --git a/experimental/packages/exporter-logs-otlp-http/test/browser/OTLPLogExporter.test.ts b/experimental/packages/exporter-logs-otlp-http/test/browser/OTLPLogExporter.test.ts index 66958a9fef7..1ca3368308c 100644 --- a/experimental/packages/exporter-logs-otlp-http/test/browser/OTLPLogExporter.test.ts +++ b/experimental/packages/exporter-logs-otlp-http/test/browser/OTLPLogExporter.test.ts @@ -14,14 +14,32 @@ * limitations under the License. */ +import { diag } from '@opentelemetry/api'; import * as assert from 'assert'; import * as sinon from 'sinon'; +import * as http from 'http'; import * as Config from '../../src/platform/config'; import { OTLPLogExporter } from '../../src/platform/browser'; +import { OTLPExporterConfigBase } from '@opentelemetry/otlp-exporter-base'; +import { ReadableLogRecord } from '@opentelemetry/sdk-logs'; +import { MockedResponse, ensureExportLogsServiceRequestIsSet, ensureExportedLogRecordIsCorrect, mockedReadableLogRecord } from '../logHelper'; +import { PassThrough, Stream } from 'stream'; +import { IExportLogsServiceRequest } from '@opentelemetry/otlp-transformer'; +import { ExportResultCode } from '@opentelemetry/core'; + +let fakeRequest: PassThrough; describe('OTLPLogExporter', () => { let envSource: Record; + let collectorExporter: OTLPLogExporter; + let collectorExporterConfig: OTLPExporterConfigBase; + let logs: ReadableLogRecord[]; + + afterEach(() => { + fakeRequest = new Stream.PassThrough(); + sinon.restore(); + }); if (typeof process === 'undefined') { envSource = globalThis as unknown as Record; @@ -61,4 +79,130 @@ describe('OTLPLogExporter', () => { assert.strictEqual(getDefaultUrl.callCount, 2); }); }); + + describe('export', () => { + beforeEach(() => { + collectorExporterConfig = { + headers: { + foo: 'bar', + }, + hostname: 'foo', + url: 'http://foo.bar.com' + }; + collectorExporter = new OTLPLogExporter(collectorExporterConfig); + logs = []; + logs.push(Object.assign({}, mockedReadableLogRecord)); + }); + afterEach(() => { + sinon.restore(); + }); + + it('should open the connection', done => { + sinon.stub(http, 'request').callsFake((options: any, cb: any) => { + assert.strictEqual(options.hostname, 'foo.bar.com'); + assert.strictEqual(options.method, 'POST'); + assert.strictEqual(options.path, '/'); + + const mockRes = new MockedResponse(200); + cb(mockRes); + mockRes.send('success'); + done(); + return fakeRequest as any; + }); + collectorExporter.export(logs, () => {}); + }); + + it('should set custom headers', done => { + + sinon.stub(http, 'request').callsFake((options: any, cb: any) => { + assert.strictEqual(options.headers['foo'], 'bar'); + + const mockRes = new MockedResponse(200); + cb(mockRes); + mockRes.send('success'); + done(); + return fakeRequest as any; + }); + + collectorExporter.export(logs, () => {}); + }); + + it('should have keep alive and keepAliveMsecs option set', done => { + + sinon.stub(http, 'request').callsFake((options: any, cb: any) => { + assert.strictEqual(options.agent.keepAlive, true); + assert.strictEqual(options.agent.options.keepAliveMsecs, 2000); + + const mockRes = new MockedResponse(200); + cb(mockRes); + mockRes.send('success'); + done(); + return fakeRequest as any; + }); + + collectorExporter.export(logs, () => {}); + }); + + it('should successfully send the logs', done => { + const fakeRequest = new Stream.PassThrough(); + sinon.stub(http, 'request').returns(fakeRequest as any); + + let buff = Buffer.from(''); + fakeRequest.on('end', () => { + const responseBody = buff.toString(); + const json = JSON.parse(responseBody) as IExportLogsServiceRequest; + const log1 = json.resourceLogs?.[0].scopeLogs?.[0].logRecords?.[0]; + assert.ok(typeof log1 !== 'undefined', "log doesn't exist"); + ensureExportedLogRecordIsCorrect(log1); + + ensureExportLogsServiceRequestIsSet(json); + + done(); + }); + + fakeRequest.on('data', chunk => { + buff = Buffer.concat([buff, chunk]); + }); + + const clock = sinon.useFakeTimers(); + collectorExporter.export(logs, () => {}); + clock.tick(200); + clock.restore(); + }); + + it('should log the successful message', done => { + // Need to stub/spy on the underlying logger as the "diag" instance is global + const spyLoggerError = sinon.stub(diag, 'error'); + + sinon.stub(http, 'request').callsFake((options: any, cb: any) => { + const mockRes = new MockedResponse(200); + cb(mockRes); + mockRes.send('success'); + return fakeRequest as any; + }); + + collectorExporter.export(logs, result => { + assert.strictEqual(result.code, ExportResultCode.SUCCESS); + assert.strictEqual(spyLoggerError.args.length, 0); + done(); + }); + }); + + it('should log the error message', done => { + sinon.stub(http, 'request').callsFake((options: any, cb: any) => { + const mockResError = new MockedResponse(400); + cb(mockResError); + mockResError.send('failed'); + + return fakeRequest as any; + }); + + collectorExporter.export(logs, result => { + assert.strictEqual(result.code, ExportResultCode.FAILED); + // @ts-expect-error verify error code + assert.strictEqual(result.error.code, 400); + done(); + }); + }); + }); });