ac-logger 2.2.3 → 2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.ncurc.js ADDED
@@ -0,0 +1,8 @@
1
+ // List packages for minor updates
2
+ const minorUpdatePackages = ['chai']
3
+
4
+ module.exports = {
5
+ target: packageName => {
6
+ return minorUpdatePackages.includes(packageName) ? 'minor' : 'latest'
7
+ }
8
+ }
package/CHANGELOG.md CHANGED
@@ -1,3 +1,28 @@
1
+ <a name="2.3.1"></a>
2
+
3
+ ## [2.3.1](https://github.com/admiralcloud/ac-logger/compare/v2.3.0..v2.3.1) (2024-03-10 12:31:44)
4
+
5
+
6
+ ### Bug Fix
7
+
8
+ * **App:** Minor improvement for backwards compatibily | MP | [462a6c38269dafccde1f18ffd8a079ac19022269](https://github.com/admiralcloud/ac-logger/commit/462a6c38269dafccde1f18ffd8a079ac19022269)
9
+ Minor improvement for backwards compatibility
10
+ Related issues: [undefined/undefined#master](undefined/browse/master)
11
+ <a name="2.3.0"></a>
12
+
13
+ # [2.3.0](https://github.com/admiralcloud/ac-logger/compare/v2.2.3..v2.3.0) (2024-03-10 11:09:52)
14
+
15
+
16
+ ### Feature
17
+
18
+ * **App:** Improved application logs | MP | [5d505c148e44045daf40909092542d7e96949f74](https://github.com/admiralcloud/ac-logger/commit/5d505c148e44045daf40909092542d7e96949f74)
19
+ ac-logger now supports machine readable application logs (if enabled). This way analyzing logs in Cloudwatch (or other apps) will be much easier.
20
+ Related issues: [undefined/undefined#master](undefined/browse/master)
21
+ ### Chores
22
+
23
+ * **App:** Updated packages | MP | [d2fe6c8ade4f1058e0315c30a8f4c85686f03181](https://github.com/admiralcloud/ac-logger/commit/d2fe6c8ade4f1058e0315c30a8f4c85686f03181)
24
+ Updated packages
25
+ Related issues: [undefined/undefined#master](undefined/browse/master)
1
26
  <a name="2.2.3"></a>
2
27
 
3
28
  ## [2.2.3](https://github.com/admiralcloud/ac-logger/compare/v2.2.2..v2.2.3) (2024-01-01 13:10:51)
package/README.md CHANGED
@@ -14,12 +14,16 @@ const aclog = require('ac-logger')
14
14
  const logConfig = {
15
15
  prefixFields: [
16
16
  { field: 'jobId', short: 'J' },
17
- ]
17
+ ],
18
+ applicationLogs: {
19
+ enabled: true // if true, JSON formatted, machine readable application logs will be created into logs directory
20
+ }
18
21
  }
19
22
 
20
23
  app = {} //
21
24
  app.log = aclog({ prefixFields: logConfig.prefixFields }).acLogger
22
25
 
26
+ // DEPRECATED
23
27
  let logMeta = {
24
28
  fileName: 'UploadS3',
25
29
  functionName: 'upload',
@@ -28,6 +32,9 @@ let logMeta = {
28
32
  app.log.info('Message for %s', 'some string', { meta: logMeta })
29
33
  // -> INFO UploadS3 | upload | J 193 | Message for some string
30
34
 
35
+ // MODERN APPROACH (that is best suited for machine and human readability)
36
+ app.log.info('Message for some string', { functionName: 'upload', ... }) // all meta data like "functionName" will be available in application logs
37
+
31
38
  ```
32
39
 
33
40
  ## Error logging
package/index.js CHANGED
@@ -1,43 +1,125 @@
1
1
  const _ = require('lodash')
2
2
  const moment = require('moment')
3
3
  const path = require('path')
4
+ const util = require('util')
4
5
 
5
6
  const { createLogger, format, transports, addColors } = require('winston')
6
7
  require('winston-daily-rotate-file')
7
8
 
8
- module.exports = ({ prefixFields = [], timestampFormat = 'YYYY-MM-DD HH:mm:ss', level = (process.env.NODE_ENV === 'production' ? 'info' : 'verbose'), headLength = 80, padLength = 12, customLevels, transporters = [] } = {}) => {
9
+ module.exports = ({ prefixFields = [], timestampFormat = 'YYYY-MM-DD HH:mm:ss', level = (process.env.NODE_ENV === 'production' ? 'info' : 'verbose'), headLength = 80, padLength = 12, customLevels, transporters = [], applicationLogs = { enabled: false } } = {}) => {
9
10
  const precisionMap = [
10
11
  { precision: 1e6, name: 'ms' },
11
12
  { precision: 1e3, name: 'µs' },
12
13
  { precision: 1, name: 'ns' },
13
14
  ]
14
15
 
15
- const myFormat = format.printf(({ timestamp, level, message, meta, e }) => {
16
- const fileName = _.get(meta, 'fileName') ? _.get(meta, 'fileName') + ' | ' : ''
17
- const functionName = _.get(meta, 'functionName') ? _.get(meta, 'functionName') + ' | ' : ''
18
- const subName = _.get(meta, 'sub') ? _.get(meta, 'sub') + ' | ' : ''
19
-
20
- let prefix = []
21
- let data = []
22
- _.forEach(prefixFields, item => {
23
- if (_.get(meta, _.get(item, 'field'))) {
24
- prefix.push(_.get(item, 'short'))
25
- data.push(_.get(meta, _.get(item, 'field')))
26
- }
27
- })
28
- const prefixData = _.size(prefix) ? (_.join(prefix, '/') + ' ' + _.join(data, '/') + ' | ') : ''
29
- if (e instanceof Error) {
30
- // log the stack
31
- console.error(e)
32
- if (_.get(e, 'message')) message += ' | ' + _.get(e, 'message', '')
16
+ const splatFormatter = format((data) => {
17
+ // Check if splat is available and the message contains placeholder(s)
18
+ if (data[Symbol.for('splat')] && /%[sjdifoO%]/.test(data.message)) {
19
+ data.message = util.format(data.message, ...data[Symbol.for('splat')])
20
+ }
21
+ return data
22
+ })
23
+
24
+ const myFormat = format.printf((data) => {
25
+ const level = data[Symbol.for('level')]
26
+ // http log format
27
+ if (level === 'http' && data?.controller) {
28
+ // display accessKey, link (if applicable)
29
+ let message = data?.message ? ` ${data.message}` : ''
30
+ let cuid = data?.customerId ? data?.customerId : ''
31
+ if (data?.userId) cuid += `/${data.userId} `
32
+ return `${data?.timestamp} ${data?.level} ${data?.ip} ${data?.iso2 || ''} ${data?.accessKey || ''} ${cuid}${data?.controller} ${data?.action}${message} | ${data?.statusCode} | ${data?.performance?.executionTime}ms`
33
+ }
34
+ else {
35
+ const meta = data?.meta
36
+ //
37
+ let fileName = _.get(meta, 'fileName') ? _.get(meta, 'fileName') + ' | ' : ''
38
+ let functionName = _.get(meta, 'functionName') ? _.get(meta, 'functionName') + ' | ' : _.get(data, 'functionName') ? _.get(data, 'functionName') + ' | ' : ''
39
+ let subName = _.get(meta, 'sub') ? _.get(meta, 'sub') + ' | ' : _.get(data, 'sub') ? _.get(data, 'sub') + ' | ' : ''
40
+
41
+ // modern approach (fileName, functionName, sub) // TBD with team
42
+ let functionIdentifier = _.get(data, 'functionIdentifier') ? _.get(data, 'functionIdentifier') + ' | ' : ''
43
+ if (!fileName && _.get(data, 'fileName')) fileName = _.get(data, 'fileName')
44
+ if (!functionName && _.get(data, 'functionName')) functionName = _.get(data, 'functionName')
45
+ if (!subName && _.get(data, 'sub')) subName = _.get(data, 'sub')
46
+
47
+
48
+ let message = data?.message
49
+
50
+
51
+ let prefix = []
52
+ let dataFromPrefix = []
53
+ _.forEach(prefixFields, item => {
54
+ if (_.get(meta, _.get(item, 'field'))) {
55
+ prefix.push(_.get(item, 'short'))
56
+ dataFromPrefix.push(_.get(meta, _.get(item, 'field')))
57
+ }
58
+ })
59
+ const prefixData = _.size(prefix) ? (_.join(prefix, '/') + ' ' + _.join(dataFromPrefix, '/') + ' | ') : ''
60
+ if (data?.e instanceof Error) {
61
+ // log the stack
62
+ console.error(data?.e)
63
+ if (data?.e?.message) message += ' | ' + ( data?.e?.message || '')
64
+ }
65
+ // TODO: for better readability, we shold use padding for fileName, functionname, etc
66
+ return `${data?.timestamp} ${data?.level} ${fileName}${functionName}${functionIdentifier}${subName}${prefixData}${message}`
33
67
  }
34
- return `${timestamp} ${level} ${fileName}${functionName}${subName}${prefixData}${message}`
35
68
  })
36
69
 
70
+
37
71
  const logTransports = []
38
72
  if (!_.size(transporters)) {
39
73
  // default behaviour
40
- logTransports.push(new transports.Console())
74
+ // human-readable console output
75
+ logTransports.push(new transports.Console({
76
+ format: format.combine(
77
+ format.timestamp({
78
+ format: timestampFormat
79
+ }),
80
+ format.errors({ stack: true }),
81
+ format(info => {
82
+ info.level = _.padEnd(info.level.toUpperCase(), 8)
83
+ return info
84
+ })(),
85
+ format.colorize(),
86
+ splatFormatter(),
87
+ myFormat
88
+ ),
89
+ }))
90
+
91
+ // structured logging for analysis and querying within the CloudWatch service.
92
+ if (applicationLogs?.enabled) {
93
+ let filename = applicationLogs?.filename || 'application.log'
94
+ if (filename.endsWith('.log')) {
95
+ // If the filename ends with .log, insert -%DATE% before the extension
96
+ filename = filename.replace(/\.log$/, '-%DATE%.log');
97
+ }
98
+ else {
99
+ // If the filename doesn't end with .log, append -%DATE%.log
100
+ filename = `${filename}-%DATE%.log`;
101
+ }
102
+
103
+ const transport = new transports.DailyRotateFile({
104
+ filename,
105
+ datePattern: 'YYYY-MM-DD',
106
+ zippedArchive: true, // Enable gzip compression for rotated files
107
+ maxSize: applicationLogs?.maxSize || '100m', // Rotate the file when it reaches 20MB
108
+ maxFiles: applicationLogs?.maxFiles || '7d', // Keep rotated files for 7 days
109
+ dirname: applicationLogs?.firname || './logs', // Specify the directory for log files
110
+ format: format.combine(
111
+ format.timestamp(),
112
+ splatFormatter(),
113
+ format.json()
114
+ )
115
+ })
116
+
117
+ transport.on('error', error => {
118
+ acLogger.error('ac-logger | ApplicationLogs | TransportError | %j', error?.message)
119
+ })
120
+ logTransports.push(transport)
121
+ }
122
+
41
123
  if (process.env.NODE_ENV === 'test') {
42
124
  logTransports.push(new transports.File({
43
125
  filename: 'test.log',
@@ -55,19 +137,6 @@ module.exports = ({ prefixFields = [], timestampFormat = 'YYYY-MM-DD HH:mm:ss',
55
137
 
56
138
  const logConfig = {
57
139
  level,
58
- format: format.combine(
59
- format.timestamp({
60
- format: timestampFormat
61
- }),
62
- format.errors({ stack: true }),
63
- format(info => {
64
- info.level = _.padEnd(info.level.toUpperCase(), 8)
65
- return info
66
- })(),
67
- format.colorize(),
68
- format.splat(),
69
- myFormat
70
- ),
71
140
  transports: logTransports
72
141
  }
73
142
  if (_.get(customLevels, 'levels')) _.set(logConfig, 'levels', _.get(customLevels, 'levels'))
@@ -76,6 +145,7 @@ module.exports = ({ prefixFields = [], timestampFormat = 'YYYY-MM-DD HH:mm:ss',
76
145
  if (_.get(customLevels, 'colors')) addColors(_.get(customLevels, 'colors'))
77
146
 
78
147
 
148
+
79
149
  const headline = (params) => {
80
150
  acLogger.info('')
81
151
  const fill = _.get(params, 'headFill', '*')
package/package.json CHANGED
@@ -3,19 +3,19 @@
3
3
  "author": "Mark Poepping (https://www.admiralcloud.com)",
4
4
  "license": "MIT",
5
5
  "repository": "admiralcloud/ac-logger",
6
- "version": "2.2.3",
6
+ "version": "2.3.1",
7
7
  "dependencies": {
8
8
  "lodash": "^4.17.21",
9
9
  "moment": "^2.30.1",
10
- "winston": "^3.11.0",
11
- "winston-daily-rotate-file": "^4.7.1"
10
+ "winston": "^3.12.0",
11
+ "winston-daily-rotate-file": "^5.0.0"
12
12
  },
13
13
  "devDependencies": {
14
14
  "ac-semantic-release": "^0.4.2",
15
- "chai": "^4.3.10",
16
- "eslint": "^8.56.0",
15
+ "chai": "^4.4.1",
16
+ "eslint": "^8.57.0",
17
17
  "intercept-stdout": "^0.1.2",
18
- "mocha": "^10.2.0",
18
+ "mocha": "^10.3.0",
19
19
  "mocha-jenkins-reporter": "^0.4.8"
20
20
  },
21
21
  "scripts": {