pip-services3-aws-node
Version:
AWS-specific components for Pip.Services in Node.js
346 lines (305 loc) • 13.2 kB
text/typescript
/** @module log */
/** @hidden */
let async = require('async');
import { IReferenceable } from 'pip-services3-commons-node';
import { LogLevel } from 'pip-services3-components-node';
import { IReferences } from 'pip-services3-commons-node';
import { IOpenable } from 'pip-services3-commons-node';
import { CachedLogger } from 'pip-services3-components-node';
import { LogMessage } from 'pip-services3-components-node';
import { ConfigException } from 'pip-services3-commons-node';
import { ConfigParams } from 'pip-services3-commons-node';
import { AwsConnectionResolver } from '../connect';
import { AwsConnectionParams } from '../connect';
import { CompositeLogger } from 'pip-services3-components-node';
import { ContextInfo } from 'pip-services3-components-node';
import { Descriptor } from 'pip-services3-commons-node'
/**
* Logger that writes log messages to AWS Cloud Watch Log.
*
* ### Configuration parameters ###
*
* - stream: (optional) Cloud Watch Log stream (default: context name)
* - group: (optional) Cloud Watch Log group (default: context instance ID or hostname)
* - connections:
* - discovery_key: (optional) a key to retrieve the connection from [[https://pip-services3-node.github.io/pip-services3-components-node/interfaces/connect.idiscovery.html IDiscovery]]
* - region: (optional) AWS region
* - credentials:
* - store_key: (optional) a key to retrieve the credentials from [[https://pip-services3-node.github.io/pip-services3-components-node/interfaces/auth.icredentialstore.html ICredentialStore]]
* - access_id: AWS access/client id
* - access_key: AWS access/client id
* - options:
* - interval: interval in milliseconds to save current counters measurements (default: 5 mins)
* - reset_timeout: timeout in milliseconds to reset the counters. 0 disables the reset (default: 0)
*
* ### References ###
*
* - <code>\*:context-info:\*:\*:1.0</code> (optional) [[https://pip-services3-node.github.io/pip-services3-components-node/classes/info.contextinfo.html ContextInfo]] to detect the context id and specify counters source
* - <code>\*:discovery:\*:\*:1.0</code> (optional) [[https://pip-services3-node.github.io/pip-services3-components-node/interfaces/connect.idiscovery.html IDiscovery]] services to resolve connections
* - <code>\*:credential-store:\*:\*:1.0</code> (optional) Credential stores to resolve credentials
*
* @see [[https://pip-services3-node.github.io/pip-services3-components-node/classes/count.counter.html Counter]] (in the Pip.Services components package)
* @see [[https://pip-services3-node.github.io/pip-services3-components-node/classes/count.cachedcounters.html CachedCounters]] (in the Pip.Services components package)
* @see [[https://pip-services3-node.github.io/pip-services3-components-node/classes/log.compositelogger.html CompositeLogger]] (in the Pip.Services components package)
*
* ### Example ###
*
* let logger = new Logger();
* logger.config(ConfigParams.fromTuples(
* "stream", "mystream",
* "group", "mygroup",
* "connection.region", "us-east-1",
* "connection.access_id", "XXXXXXXXXXX",
* "connection.access_key", "XXXXXXXXXXX"
* ));
* logger.setReferences(References.fromTuples(
* new Descriptor("pip-services", "logger", "console", "default", "1.0"),
* new ConsoleLogger()
* ));
*
* logger.open("123", (err) => {
* ...
* });
*
* logger.setLevel(LogLevel.debug);
*
* logger.error("123", ex, "Error occured: %s", ex.message);
* logger.debug("123", "Everything is OK.");
*/
export class CloudWatchLogger extends CachedLogger implements IReferenceable, IOpenable {
private _timer: any;
private _connectionResolver: AwsConnectionResolver = new AwsConnectionResolver();
private _client: any = null; //AmazonCloudWatchLogsClient
private _connection: AwsConnectionParams;
private _connectTimeout: number = 30000;
private _group: string = "undefined";
private _stream: string = null;
private _lastToken: string = null;
private _logger: CompositeLogger = new CompositeLogger();
/**
* Creates a new instance of this logger.
*/
public constructor() {
super();
}
/**
* Configures component by passing configuration parameters.
*
* @param config configuration parameters to be set.
*/
public configure(config: ConfigParams): void {
super.configure(config);
this._connectionResolver.configure(config);
this._group = config.getAsStringWithDefault('group', this._group);
this._stream = config.getAsStringWithDefault('stream', this._stream);
this._connectTimeout = config.getAsIntegerWithDefault("options.connect_timeout", this._connectTimeout);
}
/**
* Sets references to dependent components.
*
* @param references references to locate the component dependencies.
* @see [[https://pip-services3-node.github.io/pip-services3-commons-node/interfaces/refer.ireferences.html IReferences]] (in the Pip.Services commons package)
*/
public setReferences(references: IReferences): void {
super.setReferences(references);
this._logger.setReferences(references);
this._connectionResolver.setReferences(references);
let contextInfo = references.getOneOptional<ContextInfo>(
new Descriptor("pip-services", "context-info", "default", "*", "1.0"));
if (contextInfo != null && this._stream == null)
this._stream = contextInfo.name;
if (contextInfo != null && this._group == null)
this._group = contextInfo.contextId;
}
/**
* Writes a log message to the logger destination.
*
* @param level a log level.
* @param correlationId (optional) transaction id to trace execution through call chain.
* @param error an error object associated with this message.
* @param message a human-readable message to log.
*/
protected write(level: LogLevel, correlationId: string, ex: Error, message: string): void {
if (this._level < level) {
return;
}
super.write(level, correlationId, ex, message);
}
/**
* Checks if the component is opened.
*
* @returns true if the component has been opened and false otherwise.
*/
public isOpen(): boolean {
return this._timer != null;
}
/**
* Opens the component.
*
* @param correlationId (optional) transaction id to trace execution through call chain.
* @param callback callback function that receives error or null no errors occured.
*/
public open(correlationId: string, callback: (err: any) => void): void {
if (this.isOpen()) {
callback(null);
return;
}
async.series([
(callback) => {
this._connectionResolver.resolve(correlationId, (err, connection) => {
this._connection = connection;
callback(err);
});
},
(callback) => {
let aws = require('aws-sdk');
aws.config.update({
accessKeyId: this._connection.getAccessId(),
secretAccessKey: this._connection.getAccessKey(),
region: this._connection.getRegion()
});
aws.config.httpOptions = {
timeout: this._connectTimeout
};
this._client = new aws.CloudWatchLogs({ apiVersion: '2014-03-28' });
let params = {
logGroupName: this._group
};
this._client.createLogGroup(params, (err, data) => {
if (err && err.code != "ResourceAlreadyExistsException") {
callback(err);
}
else {
callback();
}
});
},
(callback) => {
let paramsStream = {
logGroupName: this._group,
logStreamName: this._stream
};
this._client.createLogStream(paramsStream, (err, data) => {
if (err) {
if (err.code == "ResourceAlreadyExistsException") {
let params = {
logGroupName: this._group,
logStreamNamePrefix: this._stream,
};
this._client.describeLogStreams(params, (err, data) => {
if (data.logStreams.length > 0) {
this._lastToken = data.logStreams[0].uploadSequenceToken;
}
callback(err)
});
}
else {
callback(err);
}
}
else {
this._lastToken = null;
callback(err);
}
});
},
(callback) => {
if (this._timer == null) {
this._timer = setInterval(() => { this.dump() }, this._interval);
}
callback();
}
], callback);
}
/**
* Closes component and frees used resources.
*
* @param correlationId (optional) transaction id to trace execution through call chain.
* @param callback callback function that receives error or null no errors occured.
*/
public close(correlationId: string, callback: (err: any) => void): void {
this.save(this._cache, (err) => {
if (this._timer)
clearInterval(this._timer);
this._cache = [];
this._timer = null;
this._client = null;
if (callback) callback(null);
});
}
private formatMessageText(message: LogMessage): string {
let result: string = "";
result += "[" + (message.source ? message.source : "---") + ":" +
(message.correlation_id ? message.correlation_id : "---") + ":" + message.level + "] " +
message.message;
if (message.error != null) {
if (!message.message) {
result += "Error: ";
} else {
result += ": ";
}
result += message.error.message;
if (message.error.stack_trace) {
result += " StackTrace: " + message.error.stack_trace;
}
}
return result;
}
/**
* Saves log messages from the cache.
*
* @param messages a list with log messages
* @param callback callback function that receives error or null for success.
*/
protected save(messages: LogMessage[], callback: (err: any) => void): void {
if (!this.isOpen() || messages == null || messages.length == 0) {
if (callback) callback(null);
return;
}
if (this._client == null) {
let err = new ConfigException("cloudwatch_logger", 'NOT_OPENED', 'CloudWatchLogger is not opened');
if (err != null) {
callback(err);
return;
}
}
let events = [];
messages.forEach(message => {
events.push({
timestamp: message.time.getTime(),
message: this.formatMessageText(message)
});
});
let params = {
logEvents: events,
logGroupName: this._group,
logStreamName: this._stream,
sequenceToken: this._lastToken
};
async.series([
(callback) => {
// get token again if saving log from another container
let describeParams = {
logGroupName: this._group,
logStreamNamePrefix: this._stream,
}
this._client.describeLogStreams(describeParams, (err, data) => {
if (data.logStreams.length > 0) {
this._lastToken = data.logStreams[0].uploadSequenceToken;
}
callback();
});
},
(callback) => {
this._client.putLogEvents(params, (err, data) => {
if (err) {
if (this._logger) this._logger.error("cloudwatch_logger", err, "putLogEvents error");
} else {
this._lastToken = data.nextSequenceToken;
}
callback();
});
}
]);
}
}