UNPKG

@google-cloud/pino-logging-gcp-config

Version:

Module to create a basic Pino LoggerConfig to support Google Cloud structured logging

165 lines (135 loc) 5.31 kB
/** * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // eslint-disable-next-line n/no-unpublished-import import 'jasmine'; import {createGcpLoggingPinoConfig, TEST_ONLY} from './pino_gcp_config'; import * as pino from 'pino'; const serviceContext = { service: 'test', version: '0.1.0', }; describe('Pino config', () => { const config = createGcpLoggingPinoConfig({serviceContext}); it('should add mixins to the config object', () => { const pinoLoggerOptions: pino.LoggerOptions = { level: 'defaultLevel', formatters: { bindings: x => { return {a: x}; }, }, }; const configWithMixins = createGcpLoggingPinoConfig( {serviceContext}, pinoLoggerOptions ); expect(configWithMixins.level).toEqual('defaultLevel'); expect(configWithMixins.formatters?.bindings).toBeDefined(); }); describe('Log level conversion', () => { // Need to define the return type of levelConverter because pino leaves it // as a plain `object` // eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain const levelConverter = config.formatters?.level! as ( label: string, level: number ) => Record<string, unknown>; Object.entries(TEST_ONLY.PINO_TO_GCP_LOG_LEVELS).forEach(e => { it(`converts pino level ${e[0]} to GCP ${e[1]}`, () => { const convertedLevel = levelConverter(e[0], 42); expect(convertedLevel.severity).toEqual(e[1]); }); }); it('defaults to INFO for an unknown level', () => { expect(levelConverter('blah', 66).severity).toEqual('INFO'); }); }); describe('log formatter', () => { // eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain const formatLogObject = config.formatters?.log!; const TEST_DATE = new Date('2024-04-13T16:12:34.123Z'); beforeEach(() => { jasmine.clock().install(); jasmine.clock().mockDate(TEST_DATE); }); afterEach(() => { jasmine.clock().uninstall(); }); it('should add serviceContext', () => { const logObject = formatLogObject({}); expect(logObject.serviceContext).toEqual({ service: 'test', version: '0.1.0', }); }); it('should add insertId', () => { const logObject = formatLogObject({}); expect(logObject['logging.googleapis.com/insertId']).toBeDefined(); expect( (logObject['logging.googleapis.com/insertId'] as string).length ).toBeGreaterThan(1); }); it('should add stack_trace when err is an Error', () => { const logObject = formatLogObject({ err: new Error('some error message'), message: 'thrown an exception', }); expect(logObject.stack_trace).toMatch('Error: some error message\n'); }); it('should not add stack_trace when err is not an Error', () => { const logObject = formatLogObject({ err: 'some error message', message: 'thrown a string as an exception', }); expect(logObject.stack_trace).toBeUndefined(); }); it('should not map span and trace properties when not present', () => { const formattedLog = formatLogObject({message: 'hello'}); expect( formattedLog['logging.googleapis.com/trace_sampled'] ).toBeUndefined(); expect(formattedLog['logging.googleapis.com/trace']).toBeUndefined(); expect(formattedLog['logging.googleapis.com/spanId']).toBeUndefined(); }); it('should replace span and trace properties when not present', () => { const formattedLog = formatLogObject({ message: 'hello', trace_id: 'trace:12345', span_id: 'span:23456', trace_flags: 1, }); expect(formattedLog['logging.googleapis.com/trace_sampled']).toBeTrue(); expect(formattedLog['logging.googleapis.com/trace']).toBe('trace:12345'); expect(formattedLog['logging.googleapis.com/spanId']).toBe('span:23456'); expect(formattedLog.trace_id).toBeUndefined(); expect(formattedLog.span_id).toBeUndefined(); expect(formattedLog.trace_flags).toBeUndefined(); }); it('adds a timestamp as seconds:nanos JSON fragment', () => { const timestampGenerator = config.timestamp as () => string; expect(timestampGenerator()).toEqual( ',"timestamp":{"seconds":1713024754,"nanos":123000000}' ); }); it('adds a timestamp as seconds:nanos JSON fragment when nanos is 0', () => { jasmine.clock().mockDate(new Date('2024-04-13T16:12:34.000Z')); const timestampGenerator = config.timestamp as () => string; expect(timestampGenerator()).toEqual( ',"timestamp":{"seconds":1713024754,"nanos":0}' ); }); }); });