"use strict";
/*!
* Copyright 2017 Google Inc. All Rights Reserved.
*
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
const stream_1 = require("stream");
const express = require("./middleware/express");
exports.express = express;
const { Logging, detectServiceContext } = require('@google-cloud/logging');
// Map of Stackdriver logging levels.
const BUNYAN_TO_STACKDRIVER = new Map([
[60, 'CRITICAL'],
[50, 'ERROR'],
[40, 'WARNING'],
[30, 'INFO'],
[20, 'DEBUG'],
[10, 'DEBUG'],
]);
/**
* Key to use in the Bunyan payload to allow users to indicate a trace for the
* request, and to store as an intermediate value on the log entry before it
* gets written to the Stackdriver logging API.
*/
exports.LOGGING_TRACE_KEY = 'logging.googleapis.com/trace';
/**
* Gets the current fully qualified trace ID when available from the
* @google-cloud/trace-agent library in the LogEntry.trace field format of:
* "projects/[PROJECT-ID]/traces/[TRACE-ID]".
*/
function getCurrentTraceFromAgent() {
const agent = global._google_trace_agent;
if (!agent || !agent.getCurrentContextId || !agent.getWriterProjectId) {
return null;
}
const traceId = agent.getCurrentContextId();
if (!traceId) {
return null;
}
const traceProjectId = agent.getWriterProjectId();
if (!traceProjectId) {
return null;
}
return `projects/${traceProjectId}/traces/${traceId}`;
}
/**
* This module provides support for streaming your Bunyan logs to
* [Stackdriver Logging](https://cloud.google.com/logging).
*
* @class
*
* @param {object} [options]
* @param {string} [options.logName] The name of the log that will receive
* messages written to this bunyan stream. Default: `bunyan_Log`.
* @param {object} [options.resource] The monitored resource that the log
* stream corresponds to. On Google Cloud Platform, this is detected
* automatically, but you may optionally specify a specific monitored
* resource. For more information, see the
* [official documentation]{@link
* https://cloud.google.com/logging/docs/api/reference/rest/v2/MonitoredResource}
* @param {object} [options.serviceContext] For logged errors, we provide this
* as the service context. For more information see
* [this guide]{@link
* https://cloud.google.com/error-reporting/docs/formatting-error-messages} and
* the [official documentation]{@link
* https://cloud.google.com/error-reporting/reference/rest/v1beta1/ServiceContext}.
* @param {string} [options.serviceContext.service] An identifier of the
* service, such as the name of the executable, job, or Google App Engine
* service name.
* @param {string} [options.serviceContext.version] Represents the version of
* the service.
* @param {string} [options.projectId] The project ID from the Google Cloud
* Console, e.g. 'grape-spaceship-123'. We will also check the environment
* variable `GCLOUD_PROJECT` for your project ID. If your app is running in
* an environment which supports {@link
* https://cloud.google.com/docs/authentication/production#providing_credentials_to_your_application
* Application Default Credentials}, your project ID will be detected
* automatically.
* @param {string} [options.keyFilename] Full path to the a .json, .pem, or .p12
* key downloaded from the Google Cloud Console. If you provide a path
* to a JSON file, the `projectId` option above is not necessary. NOTE: .pem
* and .p12 require you to specify the `email` option as well.
* @param {string} [options.email] Account email address. Required when using a
* .pem or .p12 keyFilename.
* @param {object} [options.credentials] Credentials object.
* @param {string} [options.credentials.client_email]
* @param {string} [options.credentials.private_key]
* @param {boolean} [options.autoRetry=true] Automatically retry requests if the
* response is related to rate limits or certain intermittent server errors.
* We will exponentially backoff subsequent requests by default.
* @param {number} [options.maxRetries=3] Maximum number of automatic retries
* attempted before returning the error.
* @param {constructor} [options.promise] Custom promise module to use instead
* of native Promises.
*
* @example <caption>Import the client library</caption>
* const {LoggingBunyan} = require('@google-cloud/logging-bunyan');
*
* @example <caption>Create a client that uses <a
* href="https://cloud.google.com/docs/authentication/production#providing_credentials_to_your_application">Application
* Default Credentials (ADC)</a>:</caption> const loggingBunyan = new
* LoggingBunyan();
*
* @example <caption>Create a client with <a
* href="https://cloud.google.com/docs/authentication/production#obtaining_and_providing_service_account_credentials_manually">explicit
* credentials</a>:</caption> const loggingBunyan = new LoggingBunyan({
* projectId: 'your-project-id',
* keyFilename: '/path/to/keyfile.json'
* });
*
* @example <caption>include:samples/quickstart.js</caption>
* region_tag:logging_bunyan_quickstart
* Full quickstart example:
*
*/
class LoggingBunyan extends stream_1.Writable {
constructor(options) {
options = options || {};
super({ objectMode: true });
this.logName = options.logName || 'bunyan_log';
this.resource = options.resource;
this.serviceContext = options.serviceContext;
this.stackdriverLog = new Logging(options).log(this.logName, {
removeCircular: true,
});
// serviceContext.service is required by the Error Reporting
// API. Without it, errors that are logged with level 'error'
// or higher will not be displayed in the Error Reporting
// console.
//
// As a result, if serviceContext is specified, it is required
// that serviceContext.service is specified.
if (this.serviceContext && !this.serviceContext.service) {
throw new Error(`If 'serviceContext' is specified then ` +
`'serviceContext.service' is required.`);
}
/* Asynchrnously attempt to discover the service context. */
if (!this.serviceContext) {
detectServiceContext(this.stackdriverLog.logging.auth).then((serviceContext) => {
this.serviceContext = serviceContext;
}, () => {
/* swallow any errors. */
});
}
}
/**
* Convenience method that Builds a bunyan stream object that you can put in
* the bunyan streams list.
*/
stream(level) {
return { level, type: 'raw', stream: this };
}
/**
* Format a bunyan record into a Stackdriver log entry.
*/
formatEntry_(record) {
if (typeof record === 'string') {
throw new Error('@google-cloud/logging-bunyan only works as a raw bunyan stream type.');
}
// Stackdriver Log Viewer picks up the summary line from the 'message' field
// of the payload. Unless the user has provided a 'message' property also,
// move the 'msg' to 'message'.
if (!record.message) {
// If this is an error, report the full stack trace. This allows
// Stackdriver Error Reporting to pick up errors automatically (for
// severity 'error' or higher). In this case we leave the 'msg' property
// intact.
// https://cloud.google.com/error-reporting/docs/formatting-error-messages
//
if (record.err && record.err.stack) {
record.message = record.err.stack;
record.serviceContext = this.serviceContext;
}
else if (record.msg) {
// Simply rename `msg` to `message`.
record.message = record.msg;
delete record.msg;
}
}
const entryMetadata = {
resource: this.resource,
timestamp: record.time,
severity: BUNYAN_TO_STACKDRIVER.get(Number(record.level)),
};
// If the record contains a httpRequest property, provide it on the entry
// metadata. This allows Stackdriver to use request log formatting.
// https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#HttpRequest
// Note that the httpRequest field must properly validate as a HttpRequest
// proto message, or the log entry would be rejected by the API. We do no
// validation here.
if (record.httpRequest) {
entryMetadata.httpRequest = record.httpRequest;
delete record.httpRequest;
}
// If the record contains a labels property, promote it to the entry
// metadata.
// https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry
const proper = LoggingBunyan.properLabels(record.labels);
if (record.labels && proper) {
entryMetadata.labels = record.labels;
delete record.labels;
}
if (record[exports.LOGGING_TRACE_KEY]) {
entryMetadata.trace = record[exports.LOGGING_TRACE_KEY];
delete record[exports.LOGGING_TRACE_KEY];
}
return this.stackdriverLog.entry(entryMetadata, record);
}
// tslint:disable-next-line:no-any
static properLabels(labels) {
if (typeof labels !== 'object')
return false;
for (const prop in labels) {
if (typeof labels[prop] !== 'string') {
return false;
}
}
return true;
}
// Writable.write used 'any' in function signature.
// tslint:disable-next-line:no-any
write(...args) {
let record = args[0];
let encoding = null;
let callback;
if (typeof args[1] === 'string') {
encoding = args[1];
callback = args[2];
}
else {
callback = args[1];
}
record = Object.assign({}, record);
if (!record[exports.LOGGING_TRACE_KEY]) {
const trace = getCurrentTraceFromAgent();
if (trace) {
record[exports.LOGGING_TRACE_KEY] = trace;
}
}
if (encoding !== null) {
return super.write.call(this, record, encoding, callback);
}
else {
return super.write.call(this, record, callback);
}
}
/**
* Relay a log entry to the logging agent. This is called by bunyan through
* Writable#write.
*/
_write(record, encoding, callback) {
const entry = this.formatEntry_(record);
this.stackdriverLog.write(entry, callback);
}
/**
* Relay an array of log entries to the logging agent. This is called by
* bunyan through Writable#write.
*/
// Writable._write used 'any' in function signature.
_writev(chunks, callback) {
const entries = chunks.map((request) => {
return this.formatEntry_(request.chunk);
});
this.stackdriverLog.write(entries, callback);
}
}
exports.LoggingBunyan = LoggingBunyan;
module.exports.BUNYAN_TO_STACKDRIVER = BUNYAN_TO_STACKDRIVER;
/**
* Value: `logging.googleapis.com/trace`
*
* @name LoggingBunyan.LOGGING_TRACE_KEY
* @type {string}
*/
module.exports.LOGGING_TRACE_KEY = exports.LOGGING_TRACE_KEY;
//# sourceMappingURL=index.js.map