@janiscommerce/log
Advanced tools
Comparing version 2.0.0 to 3.0.0
@@ -8,2 +8,11 @@ # Changelog | ||
## [3.0.0] - 2020-01-14 | ||
### Added | ||
- Log struct and formatting | ||
### Changed | ||
- Uploading logs to Firehose instead S3 | ||
- Log `type` field now can be only a string, not a number | ||
- Snake_case fields deprecated, only camelCase | ||
## [2.0.0] - 2019-12-05 | ||
@@ -10,0 +19,0 @@ ### Added |
@@ -9,6 +9,4 @@ 'use strict'; | ||
INVALID_LOG: 1, | ||
INVALID_CLIENT: 2, | ||
S3_ERROR: 3, | ||
NO_SERVICE_NAME: 4, | ||
NO_STAGE_NAME: 5 | ||
FIREHOSE_ERROR: 2, | ||
NO_ENVIRONMENT: 3 | ||
}; | ||
@@ -15,0 +13,0 @@ |
173
lib/log.js
'use strict'; | ||
const { struct } = require('superstruct'); | ||
const EventEmmiter = require('events'); | ||
const UUID = require('uuid/v4'); | ||
const AWS = require('aws-sdk'); | ||
const LogError = require('./log-error'); | ||
const Firehose = require('./firehose-wrapper'); | ||
const MAX_ATTEMPTS = 3; | ||
const MAX_TIMEOUT = 500; | ||
const DELIVERY_STREAM_PREFIX = 'JanisTraceFirehose'; | ||
const BUCKET_PREFIX = 'janis-trace-service'; | ||
const S3 = new AWS.S3({ httpOptions: { timeout: MAX_TIMEOUT } }); | ||
const firehose = new Firehose({ | ||
region: process.env.AWS_DEFAULT_REGION, | ||
httpOptions: { timeout: MAX_TIMEOUT } | ||
}); | ||
const emitter = new EventEmmiter(); | ||
@@ -27,102 +28,128 @@ | ||
static get _stage() { | ||
static get _env() { | ||
return process.env.JANIS_ENV; | ||
} | ||
static get bucket() { | ||
static get _envs() { | ||
if(!this._bucket) | ||
this.setBucket(); | ||
return this._bucket; | ||
return { | ||
local: 'Local', | ||
beta: 'Beta', | ||
qa: 'QA', | ||
prod: 'Prod' | ||
}; | ||
} | ||
static set bucket(bucket) { | ||
this._bucket = bucket; | ||
} | ||
static get deliveryStreamName() { | ||
static setBucket() { | ||
if(!this._deliveryStreamName) | ||
this._deliveryStreamName = `${DELIVERY_STREAM_PREFIX}${this._getFormattedEnv()}`; | ||
if(typeof this._stage === 'undefined') | ||
throw new LogError('Unknown stage name', LogError.codes.NO_STAGE_NAME); | ||
this.bucket = `${BUCKET_PREFIX}-${this._stage}`; | ||
return this._deliveryStreamName; | ||
} | ||
static async add(client, log, attempts = 1) { | ||
/** | ||
* Put a log into Firehose | ||
* @param {String} client The client code who created the log | ||
* @param {Object} log The log object | ||
* @example | ||
* add('some-client', { | ||
* type: 'some-type', | ||
* entity: 'some-entity', | ||
* entityId: 'some-entityId', | ||
* message: 'some-message', | ||
* log: { | ||
* some: 'log' | ||
* } | ||
* }); | ||
*/ | ||
static async add(client, log) { | ||
try { | ||
await this._add(client, log); | ||
log = this._validateLog(log, client); | ||
} catch(err) { | ||
return emitter.emit('create-error', log, err); | ||
} | ||
if(err.name === 'LogError') | ||
return emitter.emit('create-error', log, err); | ||
return this._add(log); | ||
} | ||
if(attempts >= MAX_ATTEMPTS) { | ||
return emitter.emit('create-error', log, | ||
new LogError(`Unable to put the log into S3, max attempts reached: ${err.message}`, LogError.codes.S3_ERROR)); | ||
} | ||
return this.add(client, log, ++attempts); | ||
} | ||
/** | ||
* Sets a callback for the specified event name | ||
* @param {String} event The event name | ||
* @param {Function} callback The event callback | ||
* @example | ||
* on('create-error', (log, err) => {...}); | ||
*/ | ||
static on(event, callback) { | ||
emitter.on(event, callback); | ||
} | ||
static async _add(client, log) { | ||
static _validateLog(rawLog, client) { | ||
if(typeof log.service === 'undefined') { | ||
const logStruct = struct.partial({ | ||
id: 'string', | ||
service: 'string', | ||
entity: 'string', | ||
entityId: 'string?|number?', | ||
type: 'string', | ||
log: 'object?|array?', | ||
message: 'string?', | ||
client: 'string', | ||
userCreated: 'string?' | ||
}, { | ||
id: UUID(), | ||
service: this._serviceName, | ||
client | ||
}); | ||
if(typeof this._serviceName === 'undefined') | ||
throw new LogError('Unknown service name', LogError.codes.NO_SERVICE_NAME); | ||
try { | ||
log.service = this._serviceName; | ||
} | ||
const validLog = logStruct(rawLog); | ||
if(typeof client !== 'string') | ||
throw new LogError('Invalid or empty client', LogError.codes.INVALID_CLIENT); | ||
if(validLog.log) | ||
validLog.log = JSON.stringify(validLog.log); | ||
this._validateLog(log); | ||
return { | ||
...validLog, | ||
dateCreated: new Date().toISOString() | ||
}; | ||
const date = log.date_created ? new Date(log.date_created * 1000) : new Date(); | ||
const year = date.getFullYear(); | ||
const month = (date.getMonth() + 1).toString().padStart(2, 0); | ||
const day = date.getDate().toString() | ||
.padStart(2, 0); | ||
} catch(err) { | ||
throw new LogError(err.message, LogError.codes.INVALID_LOG); | ||
} | ||
} | ||
if(!log.date_created) | ||
log.date_created = Math.floor(date / 1000); | ||
static _getFormattedEnv() { | ||
if(!log.id) | ||
log.id = UUID(); | ||
if(this._env && this._envs[this._env]) | ||
return this._envs[this._env]; | ||
return S3.putObject({ | ||
Bucket: this.bucket, | ||
Key: `${client}/${year}/${month}/${day}/${log.service}/${log.entity}/${log.id}.json`, | ||
Body: JSON.stringify(log), | ||
ContentType: 'application/json' | ||
}).promise(); | ||
throw new LogError('Unknown environment', LogError.codes.NO_ENVIRONMENT); | ||
} | ||
static _validateLog(log) { | ||
static async _add(log, attempts = 0) { | ||
// Log should be an object (not array) | ||
if(typeof log !== 'object' || Array.isArray(log)) | ||
throw new LogError('Invalid log: should be an object', LogError.codes.INVALID_LOG); | ||
try { | ||
// Should have service property with type string | ||
if(typeof log.service !== 'string') | ||
throw new LogError('Invalid log: should have a valid service name and must be a string', LogError.codes.INVALID_LOG); | ||
await firehose.putRecord({ | ||
DeliveryStreamName: this.deliveryStreamName, | ||
Record: { | ||
Data: Buffer.from(JSON.stringify(log)) | ||
} | ||
}); | ||
// Should have entity property with type string | ||
if(typeof log.entity !== 'string') | ||
throw new LogError('Invalid log: should have a valid entity property and must be a string', LogError.codes.INVALID_LOG); | ||
} catch(err) { | ||
// Should have type property with type number or string | ||
if(typeof log.type !== 'string' && typeof log.type !== 'number') | ||
throw new LogError('Invalid log: should have a valid type property and must be a string or number', LogError.codes.INVALID_LOG); | ||
} | ||
attempts++; | ||
static on(event, callback) { | ||
emitter.on(event, callback); | ||
if(attempts >= MAX_ATTEMPTS) { | ||
return emitter.emit('create-error', log, | ||
new LogError(`Unable to put the log into firehose, max attempts reached: ${err.message}`, LogError.codes.FIREHOSE_ERROR)); | ||
} | ||
return this._add(log, attempts); | ||
} | ||
} | ||
@@ -129,0 +156,0 @@ } |
{ | ||
"name": "@janiscommerce/log", | ||
"version": "2.0.0", | ||
"description": "A package for creating logs in S3", | ||
"version": "3.0.0", | ||
"description": "A package for creating logs in Firehose", | ||
"main": "lib/log.js", | ||
@@ -38,4 +38,5 @@ "scripts": { | ||
"aws-sdk": "^2.498.0", | ||
"uuid": "^3.3.2" | ||
"uuid": "^3.3.2", | ||
"superstruct": "0.6.2" | ||
} | ||
} |
@@ -6,3 +6,3 @@ # log | ||
A package for creating logs in S3 | ||
A package for creating logs in Firehose | ||
@@ -22,14 +22,13 @@ ## Installation | ||
Parameters: `clientCode [String]`, `log [Object]` | ||
Puts the recieved log into the janis-trace-service bucket using the `clientCode` as key prefix. | ||
Puts the recieved log into the janis-trace-firehose | ||
### Log structure | ||
The `log [Object]` parameter have the following structure: | ||
- **`id [String]`** (optional): The ID of the log in UUID V4 format. Default will be auto-generated. | ||
- **`service [String]`** (optional): The service name, if this field not exists, will be obtained from the ENV (**`JANIS_SERVICE_NAME`**) | ||
- **`type [String|Number]`** (required): The log type | ||
- **`type [String]`** (required): The log type | ||
- **`entity [String]`** (required): The name of the entity that is creating the log | ||
- **`entity_id [String]`** (optional): The ID of the entity that is creating the log | ||
- **`entityId [String]`** (optional): The ID of the entity that is creating the log | ||
- **`message [String]`** (optional): A general message about the log | ||
- **`log [Object]`** (optional): This property is a JSON that includes all the technical data about your log. | ||
- **`date_created [unix_timestamp]`** (optional): The date created of the log. Default will be auto-generated. | ||
- **`id [String]`** (optional): The ID of the log in UUID V4 format. Default will be auto-generated. | ||
- **`log [Object|Array]`** (optional): This property is a JSON that includes all the technical data about your log. | ||
@@ -39,2 +38,3 @@ ### Log example | ||
{ | ||
id: '0acefd5e-cb90-4531-b27a-e4d236f07539', | ||
type: 'new', | ||
@@ -51,7 +51,6 @@ entity: 'api', | ||
'x-forwarded-proto': 'https', | ||
'x-forwarded-port': '443', | ||
'x-forwarded-port': '443' | ||
}, | ||
responseHttpCode: 200 | ||
}, | ||
id: '0acefd5e-cb90-4531-b27a-e4d236f07539' | ||
} | ||
} | ||
@@ -73,6 +72,4 @@ ``` | ||
| 1 | Invalid log | | ||
| 2 | Invalid client | | ||
| 3 | S3 Error | | ||
| 4 | Unknown service name | | ||
| 5 | Unknown stage name | | ||
| 2 | Firehose Error | | ||
| 3 | Unknown stage name | | ||
@@ -88,3 +85,3 @@ In case of error while creating your log into S3, this package will emit an event called `create-error`, you can handle it using the `on()` method. | ||
entity: 'api', | ||
entity_id: 'product', | ||
entityId: 'product', | ||
message: '[GET] Request from 0.0.0.0 of custom_data' | ||
@@ -97,10 +94,2 @@ // ... | ||
}); | ||
``` | ||
## Notes | ||
In order to connect into S3, this package requires the aws volume in the `docker-compose.yml`. | ||
```yml | ||
volumes: | ||
~/.aws:/root/.aws | ||
``` |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
9023
6
149
3
88
3
+ Addedsuperstruct@0.6.2
+ Addedclone-deep@2.0.2(transitive)
+ Addedfor-in@0.1.81.0.2(transitive)
+ Addedfor-own@1.0.0(transitive)
+ Addedis-extendable@0.1.1(transitive)
+ Addedis-plain-object@2.0.4(transitive)
+ Addedisobject@3.0.1(transitive)
+ Addedkind-of@5.1.06.0.3(transitive)
+ Addedmixin-object@2.0.1(transitive)
+ Addedshallow-clone@1.0.0(transitive)
+ Addedsuperstruct@0.6.2(transitive)