Skip to content
This repository has been archived by the owner on May 3, 2024. It is now read-only.

Commit

Permalink
feat(otel): log notice to STDOUT when using OTel (#1215)
Browse files Browse the repository at this point in the history
  • Loading branch information
10xLaCroixDrinker committed Jan 3, 2024
1 parent 0541eaa commit b4bcf21
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 31 deletions.
130 changes: 99 additions & 31 deletions __tests__/server/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ import { Map as ImmutableMap } from 'immutable';
jest.unmock('yargs');

describe('server index', () => {
jest.spyOn(console, 'log').mockImplementation(() => { });
jest.spyOn(console, 'error').mockImplementation(() => { });
jest.spyOn(console, 'log').mockImplementation(() => {});
jest.spyOn(console, 'error').mockImplementation(() => {});
jest.spyOn(process.stdout, 'write').mockImplementation(() => {});
jest.spyOn(process.stderr, 'write').mockImplementation(() => {});
const origFsExistsSync = fs.existsSync;

let addServer;
Expand Down Expand Up @@ -55,7 +57,8 @@ describe('server index', () => {
jest.doMock('@americanexpress/one-app-dev-proxy', () => ({
default: jest.fn(() => ({
listen: jest.fn((port, cb) => {
setTimeout(() => (oneAppDevProxyError ? cb(new Error('test error')) : cb(null, { port })));
setTimeout(() => (oneAppDevProxyError ? cb(new Error('test error')) : cb(null, { port }))
);
return { close: 'one-app-dev-proxy' };
}),
})),
Expand All @@ -68,21 +71,20 @@ describe('server index', () => {

jest.doMock('cross-fetch');

jest.doMock(
'../../src/server/utils/loadModules',
() => jest.fn(() => Promise.resolve())
);
jest.doMock('../../src/server/utils/loadModules', () => jest.fn(() => Promise.resolve()));
jest.doMock('babel-polyfill', () => {
// jest includes babel-polyfill
// if included twice, babel-polyfill will complain that it should be only once
});
//
jest.doMock('../../src/server/polyfill/intl');
jest.doMock('../../src/server/utils/logging/setup', () => { });
jest.doMock('../../src/server/utils/logging/setup', () => {});

ssrServerListen = jest.fn(async () => {
if (ssrServerError) {
throw new Error('test error');
const error = new Error('ssr server test error');
error.stack = `${error.toString()}\n at <anonymous>:1:1`;
throw error;
}
});
ssrServer = jest.fn(() => ({
Expand All @@ -93,7 +95,9 @@ describe('server index', () => {

metricsServerListen = jest.fn(async () => {
if (metricsServerError) {
throw new Error('test error');
const error = new Error('metrics server test error');
error.stack = `${error.toString()}\n at <anonymous>:1:1`;
throw error;
}
});
jest.doMock('../../src/server/metricsServer', () => () => ({
Expand All @@ -103,7 +107,7 @@ describe('server index', () => {

devHolocronCDNListen = jest.fn(async () => {
if (devHolocronCdnError) {
throw new Error('test error');
throw new Error('dev cdn test error');
}
});
jest.doMock('../../src/server/devHolocronCDN', () => ({
Expand All @@ -118,7 +122,7 @@ describe('server index', () => {
jest.doMock('../../src/server/shutdown', () => ({ addServer, shutdown }));
jest.doMock('../../src/server/utils/pollModuleMap', () => jest.fn());
jest.doMock('../../src/server/config/env/runTime', () => jest.fn());
jest.doMock('../../src/server/utils/heapdump', () => { });
jest.doMock('../../src/server/utils/heapdump', () => {});
jest.doMock('../../src/server/utils/watchLocalModules', () => ({ default: jest.fn() }));
jest.doMock('../../src/server/utils/getHttpsConfig', () => () => 'https-config-mock');

Expand Down Expand Up @@ -173,13 +177,15 @@ describe('server index', () => {
const endpointsFilePath = path.join(process.cwd(), '.dev', 'endpoints', 'index.js');
process.env.NODE_ENV = 'development';
fs.existsSync = () => true;
jest.doMock(endpointsFilePath, () => () => ({
oneTestEndpointUrl: {
devProxyPath: 'test',
destination: 'https://example.com',
},
}),
{ virtual: true }
jest.doMock(
endpointsFilePath,
() => () => ({
oneTestEndpointUrl: {
devProxyPath: 'test',
destination: 'https://example.com',
},
}),
{ virtual: true }
);
await load();
fs.existsSync = origFsExistsSync;
Expand All @@ -192,13 +198,15 @@ describe('server index', () => {
const endpointsFilePath = path.join(process.cwd(), '.dev', 'endpoints', 'index.js');
process.env.NODE_ENV = 'development';
fs.existsSync = () => false;
jest.doMock(endpointsFilePath, () => () => ({
oneTestEndpointUrl: {
devProxyPath: 'test',
destination: 'https://example.com',
},
}),
{ virtual: true }
jest.doMock(
endpointsFilePath,
() => () => ({
oneTestEndpointUrl: {
devProxyPath: 'test',
destination: 'https://example.com',
},
}),
{ virtual: true }
);
await load();
fs.existsSync = origFsExistsSync;
Expand Down Expand Up @@ -350,9 +358,7 @@ describe('server index', () => {

await load();

expect(ssrServerListen).toHaveBeenCalledWith(
{ host: '0.0.0.0', port: '5555' }
);
expect(ssrServerListen).toHaveBeenCalledWith({ host: '0.0.0.0', port: '5555' });
expect(ssrServer).toHaveBeenCalledWith({ https: 'https-config-mock' });
});

Expand All @@ -364,6 +370,26 @@ describe('server index', () => {
expect(util.format(...console.error.mock.calls[0])).toMatchSnapshot();
});

it('does not log a notice directly to STDERR when not using OTel and listening on the server fails', async () => {
delete process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT;
await load({ ssrServerError: true });
expect(process.stderr.write).not.toHaveBeenCalled();
});

it('logs a notice directly to STDERR when using OTel and listening on the server fails', async () => {
process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = 'http://0.0.0.0:4317/v1/logs';
await load({ ssrServerError: true });
expect(process.stderr.write).toHaveBeenCalledTimes(1);
expect(process.stderr.write.mock.calls[0][0]).toMatchInlineSnapshot(`
"
one-app failed to start. Logs are being sent to OTel via gRPC at http://0.0.0.0:4317/v1/logs
Error: ssr server test error
at <anonymous>:1:1"
`);
delete process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT;
});

it('logs errors when listening on the metrics server fails', async () => {
await load({ metricsServerError: true });

Expand All @@ -372,6 +398,26 @@ describe('server index', () => {
expect(util.format(...console.error.mock.calls[0])).toMatchSnapshot();
});

it('does not log a notice directly to STDERR when not using OTel and listening on the metrics server fails', async () => {
delete process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT;
await load({ metricsServerError: true });
expect(process.stderr.write).not.toHaveBeenCalled();
});

it('logs a notice directly to STDERR when using OTel and listening on the metrics server fails', async () => {
process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = 'http://0.0.0.0:4317/v1/logs';
await load({ metricsServerError: true });
expect(process.stderr.write).toHaveBeenCalledTimes(1);
expect(process.stderr.write.mock.calls[0][0]).toMatchInlineSnapshot(`
"
one-app failed to start. Logs are being sent to OTel via gRPC at http://0.0.0.0:4317/v1/logs
Error: metrics server test error
at <anonymous>:1:1"
`);
delete process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT;
});

it('closes servers when starting ssrServer fails', async () => {
process.env.NODE_ENV = 'development';

Expand All @@ -386,7 +432,9 @@ describe('server index', () => {
await load();

expect(console.log).toHaveBeenCalled();
expect(util.format(...console.log.mock.calls[1])).toMatch('🌎 One App server listening on port 3000');
expect(util.format(...console.log.mock.calls[1])).toMatch(
'🌎 One App server listening on port 3000'
);
});

it('logs when metrics server is successfully listening on the port', async () => {
Expand All @@ -396,7 +444,9 @@ describe('server index', () => {
await load();

expect(console.log).toHaveBeenCalled();
expect(util.format(...console.log.mock.calls[0])).toMatch('📊 Metrics server listening on port 3005');
expect(util.format(...console.log.mock.calls[0])).toMatch(
'📊 Metrics server listening on port 3005'
);
});

it('initiates module-map polling if successfully listening on port', async () => {
Expand All @@ -407,6 +457,24 @@ describe('server index', () => {
expect(pollModuleMap).toHaveBeenCalledTimes(1);
});

it('does not log a notice to STDOUT when not using OTel', async () => {
delete process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT;
await load();
expect(process.stdout.write).not.toHaveBeenCalled();
});

it('logs a notice to STDOUT when using OTel', async () => {
process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = 'http://0.0.0.0:4317/v1/logs';
await load();
expect(process.stdout.write).toHaveBeenCalledTimes(1);
expect(process.stdout.write.mock.calls[0][0]).toMatchInlineSnapshot(`
"
one-app started successfully. Logs are being sent to OTel via gRPC at http://0.0.0.0:4317/v1/logs
"
`);
delete process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT;
});

afterAll(() => {
delete process.env.ONE_CLIENT_ROOT_MODULE_NAME;
});
Expand Down
8 changes: 8 additions & 0 deletions src/server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
* permissions and limitations under the License.
*/

import util from 'node:util';
import path from 'path';
import fs from 'fs';
import Intl from 'lean-intl';
Expand Down Expand Up @@ -89,6 +90,10 @@ async function ssrServerStart() {
});

await pollModuleMap();

if (process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT) {
process.stdout.write(util.format('\none-app started successfully. Logs are being sent to OTel via gRPC at %s\n', process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT));
}
}

async function metricsServerStart() {
Expand Down Expand Up @@ -153,5 +158,8 @@ const serverChain = process.env.NODE_ENV === 'development'

export default serverChain.catch((err) => {
console.error(err);
if (process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT) {
process.stderr.write(util.format('\none-app failed to start. Logs are being sent to OTel via gRPC at %s\n\n%s', process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT, err.stack));
}
shutdown();
});

0 comments on commit b4bcf21

Please sign in to comment.