UNPKG

12.4 kBJavaScriptView Raw
1"use strict";
2// Copyright 2016 Google LLC
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15Object.defineProperty(exports, "__esModule", { value: true });
16exports.getNodejsLibraryVersion = exports.LoggingCommon = exports.getCurrentTraceFromAgent = exports.NODEJS_WINSTON_DEFAULT_LIBRARY_VERSION = exports.LOGGING_SAMPLED_KEY = exports.LOGGING_SPAN_KEY = exports.LOGGING_TRACE_KEY = void 0;
17const util = require("util");
18const logging_1 = require("@google-cloud/logging");
19const instrumentation_1 = require("@google-cloud/logging/build/src/utils/instrumentation");
20const mapValues = require("lodash.mapvalues");
21// Map of npm output levels to Cloud Logging levels.
22// See https://github.com/winstonjs/winston#logging-levels for more info.
23const NPM_LEVEL_NAME_TO_CODE = {
24 error: 3,
25 warn: 4,
26 info: 6,
27 http: 6,
28 verbose: 7,
29 debug: 7,
30 silly: 7,
31};
32// Map of Cloud Logging levels.
33const CLOUD_LOGGING_LEVEL_CODE_TO_NAME = {
34 0: 'emergency',
35 1: 'alert',
36 2: 'critical',
37 3: 'error',
38 4: 'warning',
39 5: 'notice',
40 6: 'info',
41 7: 'debug',
42};
43/*!
44 * Log entry data key to allow users to indicate a trace for the request.
45 */
46exports.LOGGING_TRACE_KEY = 'logging.googleapis.com/trace';
47/*!
48 * Log entry data key to allow users to indicate a spanId for the request.
49 */
50exports.LOGGING_SPAN_KEY = 'logging.googleapis.com/spanId';
51/*!
52 * Log entry data key to allow users to indicate a traceSampled flag for the request.
53 */
54exports.LOGGING_SAMPLED_KEY = 'logging.googleapis.com/trace_sampled';
55/**
56 * Default library version to be used
57 * Using release-please annotations to update DEFAULT_INSTRUMENTATION_VERSION with latest version.
58 * See https://github.com/googleapis/release-please/blob/main/docs/customizing.md#updating-arbitrary-files
59 */
60exports.NODEJS_WINSTON_DEFAULT_LIBRARY_VERSION = '6.0.0'; // {x-release-please-version}
61/*!
62 * Gets the current fully qualified trace ID when available from the
63 * @google-cloud/trace-agent library in the LogEntry.trace field format of:
64 * "projects/[PROJECT-ID]/traces/[TRACE-ID]".
65 */
66function getCurrentTraceFromAgent() {
67 // eslint-disable-next-line @typescript-eslint/no-explicit-any
68 const agent = global._google_trace_agent;
69 if (!agent || !agent.getCurrentContextId || !agent.getWriterProjectId) {
70 return null;
71 }
72 const traceId = agent.getCurrentContextId();
73 if (!traceId) {
74 return null;
75 }
76 const traceProjectId = agent.getWriterProjectId();
77 if (!traceProjectId) {
78 return null;
79 }
80 return `projects/${traceProjectId}/traces/${traceId}`;
81}
82exports.getCurrentTraceFromAgent = getCurrentTraceFromAgent;
83class LoggingCommon {
84 constructor(options) {
85 var _a, _b;
86 options = Object.assign({
87 scopes: ['https://www.googleapis.com/auth/logging.write'],
88 }, options);
89 this.logName = options.logName || 'winston_log';
90 this.inspectMetadata = options.inspectMetadata === true;
91 this.levels = options.levels || NPM_LEVEL_NAME_TO_CODE;
92 this.redirectToStdout = (_a = options.redirectToStdout) !== null && _a !== void 0 ? _a : false;
93 if (!this.redirectToStdout) {
94 this.cloudLog = new logging_1.Logging(options).log(this.logName, {
95 removeCircular: true,
96 // See: https://cloud.google.com/logging/quotas, a log size of
97 // 250,000 has been chosen to keep us comfortably within the
98 // 256,000 limit.
99 maxEntrySize: options.maxEntrySize || 250000,
100 });
101 }
102 else {
103 const logSyncOptions = {
104 useMessageField: (_b = options.useMessageField) !== null && _b !== void 0 ? _b : true,
105 };
106 this.cloudLog = new logging_1.Logging(options).logSync(this.logName, undefined, logSyncOptions);
107 }
108 this.resource = options.resource;
109 this.serviceContext = options.serviceContext;
110 this.prefix = options.prefix;
111 this.labels = options.labels;
112 this.defaultCallback = options.defaultCallback;
113 }
114 log(level, message, metadata, callback) {
115 metadata = metadata || {};
116 // First create instrumentation record if it is never written before
117 let instrumentationEntry;
118 if (!(0, instrumentation_1.setInstrumentationStatus)(true)) {
119 instrumentationEntry = (0, instrumentation_1.createDiagnosticEntry)('nodejs-winston', getNodejsLibraryVersion());
120 // Update instrumentation record resource, logName and timestamp
121 instrumentationEntry.metadata.resource = this.resource;
122 instrumentationEntry.metadata.logName = metadata.logName;
123 instrumentationEntry.metadata.timestamp = metadata.timestamp;
124 }
125 message = message || '';
126 const hasMetadata = Object.keys(metadata).length;
127 if (this.levels[level] === undefined) {
128 throw new Error('Unknown log level: ' + level);
129 }
130 const levelCode = this.levels[level];
131 const cloudLevel = CLOUD_LOGGING_LEVEL_CODE_TO_NAME[levelCode];
132 const data = {};
133 // Cloud Logs Viewer picks up the summary line from the `message`
134 // property of the jsonPayload.
135 // https://cloud.google.com/logging/docs/view/logs_viewer_v2#expanding.
136 //
137 // For error messages at severity 'error' and higher,
138 // Error Reporting will pick up error messages if the full stack trace is
139 // included in the textPayload or the message property of the jsonPayload.
140 // https://cloud.google.com/error-reporting/docs/formatting-error-messages
141 // We prefer to format messages as jsonPayload (by putting it as a message
142 // property on an object) as that works and is accepted by Error Reporting
143 // in far more resource types.
144 //
145 if (metadata.stack) {
146 message += (message ? ' ' : '') + metadata.stack;
147 data.serviceContext = this.serviceContext;
148 }
149 data.message = this.prefix ? `[${this.prefix}] ` : '';
150 data.message += message;
151 const entryMetadata = {
152 resource: this.resource,
153 };
154 // If the metadata contains a logName property, promote it to the entry
155 // metadata.
156 if (metadata.logName) {
157 entryMetadata.logName = metadata.logName;
158 }
159 // If the metadata contains a httpRequest property, promote it to the
160 // entry metadata. This allows Cloud Logging to use request log formatting.
161 // https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#HttpRequest
162 // Note that the httpRequest field must properly validate as HttpRequest
163 // proto message, or the log entry would be rejected by the API. We no do
164 // validation here.
165 if (metadata.httpRequest) {
166 entryMetadata.httpRequest = metadata.httpRequest;
167 }
168 // If the metadata contains a timestamp property, promote it to the entry
169 // metadata. As Winston 3 buffers logs when a transport (such as this one)
170 // invokes its log callback asynchronously, a timestamp assigned at log time
171 // is more accurate than one assigned in a transport.
172 if (metadata.timestamp instanceof Date) {
173 entryMetadata.timestamp = metadata.timestamp;
174 }
175 // If the metadata contains a labels property, promote it to the entry
176 // metadata.
177 // https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry
178 if (this.labels || metadata.labels) {
179 entryMetadata.labels = !this.labels
180 ? metadata.labels
181 : Object.assign({}, this.labels, metadata.labels);
182 }
183 const trace = metadata[exports.LOGGING_TRACE_KEY] || getCurrentTraceFromAgent();
184 if (trace) {
185 entryMetadata.trace = trace;
186 }
187 const spanId = metadata[exports.LOGGING_SPAN_KEY];
188 if (spanId) {
189 entryMetadata.spanId = spanId;
190 }
191 if (exports.LOGGING_SAMPLED_KEY in metadata) {
192 entryMetadata.traceSampled = metadata[exports.LOGGING_SAMPLED_KEY] === '1';
193 }
194 // we have tests that assert that metadata is always passed.
195 // not sure if its correct but for now we always set it even if it has
196 // nothing in it
197 data.metadata = this.inspectMetadata
198 ? mapValues(metadata, util.inspect)
199 : metadata;
200 if (hasMetadata) {
201 // clean entryMetadata props
202 delete data.metadata[exports.LOGGING_TRACE_KEY];
203 delete data.metadata[exports.LOGGING_SPAN_KEY];
204 delete data.metadata[exports.LOGGING_SAMPLED_KEY];
205 delete data.metadata.httpRequest;
206 delete data.metadata.labels;
207 delete data.metadata.timestamp;
208 delete data.metadata.logName;
209 }
210 const entries = [];
211 entries.push(this.entry(entryMetadata, data));
212 // Check if instrumentation entry needs to be added as well
213 if (instrumentationEntry) {
214 // Make sure instrumentation entry is updated by underlying logger
215 instrumentationEntry = this.entry(instrumentationEntry.metadata, instrumentationEntry.data);
216 if (levelCode !== NPM_LEVEL_NAME_TO_CODE.info) {
217 // We using info level for diagnostic records
218 this.cloudLog[CLOUD_LOGGING_LEVEL_CODE_TO_NAME[NPM_LEVEL_NAME_TO_CODE.info]]([instrumentationEntry], this.defaultCallback);
219 }
220 else
221 entries.push(instrumentationEntry);
222 }
223 // Make sure that both callbacks are called in case if provided
224 const newCallback = (err, apiResponse) => {
225 let callbackError;
226 if (callback) {
227 try {
228 callback(err, apiResponse);
229 }
230 catch (error) {
231 callbackError = error;
232 }
233 }
234 if (this.defaultCallback) {
235 this.defaultCallback(err, apiResponse);
236 }
237 // In case if original error was null and callback thrown exception, rethrow it to make sure
238 // we do not swallow it since upon success the exceptions normally should not be thrown. Also
239 // we should retrhrow callbackError when defaultCallback was not provided to keep
240 // prevous behaviour intact
241 if ((!this.defaultCallback || err === null) && callbackError) {
242 throw callbackError;
243 }
244 };
245 this.cloudLog[cloudLevel](entries, newCallback);
246 // The LogSync class does not supports callback. However Writable class always
247 // provides onwrite() callback which needs to be called after each log is written,
248 // so the stream would remove writing state. Since this.defaultCallback can also be set, we
249 // should call it explicitly as well.
250 if (this.redirectToStdout) {
251 newCallback(null, undefined);
252 }
253 }
254 entry(metadata, data) {
255 if (this.redirectToStdout) {
256 return this.cloudLog.entry(metadata, data);
257 }
258 return this.cloudLog.entry(metadata, data);
259 }
260}
261exports.LoggingCommon = LoggingCommon;
262// LOGGING_TRACE_KEY is Cloud Logging specific and has the format:
263// logging.googleapis.com/trace
264LoggingCommon.LOGGING_TRACE_KEY = exports.LOGGING_TRACE_KEY;
265// LOGGING_TRACE_KEY is Cloud Logging specific and has the format:
266// logging.googleapis.com/spanId
267LoggingCommon.LOGGING_SPAN_KEY = exports.LOGGING_SPAN_KEY;
268function getNodejsLibraryVersion() {
269 return exports.NODEJS_WINSTON_DEFAULT_LIBRARY_VERSION;
270}
271exports.getNodejsLibraryVersion = getNodejsLibraryVersion;
272//# sourceMappingURL=common.js.map
\No newline at end of file