diff --git a/src/entry.ts b/src/entry.ts index c3abae5f..d3748425 100644 --- a/src/entry.ts +++ b/src/entry.ts @@ -18,7 +18,12 @@ const EventId = require('eventid'); import * as extend from 'extend'; import {google} from '../protos/protos'; -import {objToStruct, structToObj, zuluToDateObj} from './utils/common'; +import { + objToStruct, + structToObj, + zuluToDateObj, + toNanosAndSecondsObj, +} from './utils/common'; import { makeHttpRequestData, CloudLoggingHttpRequest, @@ -75,7 +80,9 @@ export interface StructuredJson { // Universally supported properties message?: string | object; httpRequest?: object; - timestamp?: string; + // Based on https://cloud.google.com/logging/docs/agent/logging/configuration#timestamp-processing, the + // timestamp should be in nanos and seconds format. + timestamp?: Timestamp; [INSERT_ID_KEY]?: string; [OPERATION_KEY]?: object; [SOURCE_LOCATION_KEY]?: object; @@ -214,12 +221,7 @@ class Entry { } // Format log timestamp if (entry.timestamp instanceof Date) { - const seconds = entry.timestamp.getTime() / 1000; - const secondsRounded = Math.floor(seconds); - entry.timestamp = { - seconds: secondsRounded, - nanos: Math.floor((seconds - secondsRounded) * 1e9), - }; + entry.timestamp = toNanosAndSecondsObj(entry.timestamp); } else if (typeof entry.timestamp === 'string') { entry.timestamp = zuluToDateObj(entry.timestamp); } @@ -300,7 +302,7 @@ class Entry { } // Format timestamp if (meta.timestamp instanceof Date) { - entry.timestamp = meta.timestamp.toISOString(); + entry.timestamp = toNanosAndSecondsObj(meta.timestamp); } // Format httprequest const req = meta.httpRequest; diff --git a/src/utils/common.ts b/src/utils/common.ts index ee05d3ff..9e3f8470 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -243,3 +243,17 @@ export function zuluToDateObj(zuluTime: string) { nanos: nanoSecs ? Number(nanoSecs.padEnd(9, '0')) : 0, }; } + +/** + * Converts Date to nanoseconds format suported by Logging. + * See https://cloud.google.com/logging/docs/agent/logging/configuration#timestamp-processing for more details + * @param date The date to be converted to Logging nanos and seconds format + */ +export function toNanosAndSecondsObj(date: Date) { + const seconds = date.getTime() / 1000; + const secondsRounded = Math.floor(seconds); + return { + seconds: secondsRounded, + nanos: Math.floor((seconds - secondsRounded) * 1e9), + }; +} diff --git a/src/utils/instrumentation.ts b/src/utils/instrumentation.ts index c99b08e7..6a6809de 100644 --- a/src/utils/instrumentation.ts +++ b/src/utils/instrumentation.ts @@ -16,7 +16,6 @@ import arrify = require('arrify'); import path = require('path'); -import {google} from '../../protos/protos'; import {Entry} from '../entry'; // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -95,25 +94,20 @@ export function createDiagnosticEntry( if (!libraryName || !libraryName.startsWith(NODEJS_LIBRARY_NAME_PREFIX)) { libraryName = NODEJS_LIBRARY_NAME_PREFIX; } - const entry = new Entry( - { - severity: google.logging.type.LogSeverity.INFO, + const entry = new Entry(undefined, { + [DIAGNOSTIC_INFO_KEY]: { + [INSTRUMENTATION_SOURCE_KEY]: [ + { + // Truncate libraryName and libraryVersion if more than 14 characters length + name: truncateValue(libraryName, maxDiagnosticValueLen), + version: truncateValue( + libraryVersion ?? getNodejsLibraryVersion(), + maxDiagnosticValueLen + ), + }, + ], }, - { - [DIAGNOSTIC_INFO_KEY]: { - [INSTRUMENTATION_SOURCE_KEY]: [ - { - // Truncate libraryName and libraryVersion if more than 14 characters length - name: truncateValue(libraryName, maxDiagnosticValueLen), - version: truncateValue( - libraryVersion ?? getNodejsLibraryVersion(), - maxDiagnosticValueLen - ), - }, - ], - }, - } - ); + }); return entry; } diff --git a/test/entry.ts b/test/entry.ts index af458dd0..0fd86e58 100644 --- a/test/entry.ts +++ b/test/entry.ts @@ -56,6 +56,14 @@ function withinExpectedTimeBoundaries(result?: Date): boolean { return false; } +function nanosAndSecondsToDate(timestamp: entryTypes.Timestamp) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const seconds = (timestamp as any).seconds; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const nanos = (timestamp as any).nanos; + return new Date(seconds * 1000 + nanos / 1e9); +} + describe('Entry', () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any let Entry: typeof entryTypes.Entry; @@ -319,7 +327,9 @@ describe('Entry', () => { entry.metadata.traceSampled = false; entry.data = 'this is a log'; const json = entry.toStructuredJSON(); - assert(withinExpectedTimeBoundaries(new Date(json.timestamp!))); + assert( + withinExpectedTimeBoundaries(nanosAndSecondsToDate(json.timestamp!)) + ); delete json.timestamp; const expectedJSON = { [entryTypes.INSERT_ID_KEY]: '👀', @@ -345,7 +355,9 @@ describe('Entry', () => { it('should convert a string timestamp', () => { entry.metadata.timestamp = new Date(); const json = entry.toStructuredJSON(); - assert(withinExpectedTimeBoundaries(new Date(json.timestamp!))); + assert( + withinExpectedTimeBoundaries(nanosAndSecondsToDate(json.timestamp!)) + ); }); it('should convert a raw http to httprequest', () => {