├── .editorconfig ├── .eslintrc ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .taprc.yml ├── Changelog.md ├── Readme.md ├── app.js ├── lib ├── rfc3164.js ├── rfc5424.js ├── transport.js └── utils.js ├── package.json ├── psyslog.js └── test ├── 3164.test.js ├── 5424.test.js ├── fixtures ├── configs │ ├── 3164 │ │ ├── appname.json │ │ ├── cee.json │ │ ├── ceeMessageOnly.json │ │ ├── custom-level.json │ │ ├── date.json │ │ ├── facility.json │ │ ├── messageOnly.json │ │ ├── newline.json │ │ ├── sync.json │ │ └── tz.json │ └── 5424 │ │ ├── appname.json │ │ ├── cee.json │ │ ├── ceeMessageOnly.json │ │ ├── custom-level.json │ │ ├── facility.json │ │ ├── messageOnly.json │ │ ├── newline.json │ │ ├── structuredData.json │ │ ├── sync.json │ │ └── tz.json ├── log-stdout.js └── messages.js ├── pipeline.test.js └── transport.test.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "standard" 4 | ], 5 | "rules": { 6 | "no-var": "off" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - 'docs/**' 7 | - '*.md' 8 | pull_request: 9 | paths-ignore: 10 | - 'docs/**' 11 | - '*.md' 12 | 13 | # This allows a subsequently queued workflow run to interrupt previous runs 14 | concurrency: 15 | group: "${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}" 16 | cancel-in-progress: true 17 | 18 | jobs: 19 | dependency-review: 20 | name: Dependency Review 21 | if: github.event_name == 'pull_request' 22 | runs-on: ubuntu-latest 23 | permissions: 24 | contents: read 25 | steps: 26 | - name: Check out repo 27 | uses: actions/checkout@v3 28 | with: 29 | persist-credentials: false 30 | 31 | - name: Dependency review 32 | uses: actions/dependency-review-action@v1 33 | 34 | test: 35 | name: Test 36 | runs-on: ${{ matrix.os }} 37 | permissions: 38 | contents: read 39 | strategy: 40 | fail-fast: false 41 | matrix: 42 | os: [ubuntu-latest] 43 | node-version: [16, 18, 19] 44 | steps: 45 | - name: Check out repo 46 | uses: actions/checkout@v3 47 | with: 48 | persist-credentials: false 49 | 50 | - name: Setup Node ${{ matrix.node-version }} 51 | uses: actions/setup-node@v3 52 | with: 53 | node-version: ${{ matrix.node-version }} 54 | 55 | - name: Install dependencies 56 | run: npm i --ignore-scripts 57 | 58 | - name: Run tests 59 | run: npm test 60 | 61 | automerge: 62 | name: Automerge Dependabot PRs 63 | if: > 64 | github.event_name == 'pull_request' && 65 | github.event.pull_request.user.login == 'dependabot[bot]' 66 | needs: test 67 | permissions: 68 | pull-requests: write 69 | contents: write 70 | runs-on: ubuntu-latest 71 | steps: 72 | - uses: fastify/github-action-merge-dependabot@v3 73 | with: 74 | github-token: ${{ secrets.GITHUB_TOKEN }} 75 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | 132 | # Vim swap files 133 | *.swp 134 | 135 | # macOS files 136 | .DS_Store 137 | 138 | # editor files 139 | .vscode 140 | .idea 141 | 142 | # lock files 143 | package-lock.json 144 | pnpm-lock.yaml 145 | yarn.lock 146 | 147 | # 0x 148 | .__browserify* 149 | profile-* -------------------------------------------------------------------------------- /.taprc.yml: -------------------------------------------------------------------------------- 1 | jobs: 1 2 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | ### 1.0.4 2 | + See [releases page](https://github.com/pinojs/pino-syslog/releases) for current and all future changes. 3 | 4 | ### 1.0.2 5 | + remove 5424 BOM prefix due to it being totally wrong (may be added back later) 6 | 7 | ### 1.0.1 8 | + add code to prevent app quitting after processing first log 9 | 10 | ### 1.0.0 11 | + refactor code base to support multiple RFCs 12 | + add support for RFC5424 13 | + make RFC5424 the default format 14 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # pino-syslog 2 | 3 | **Lead maintainer:** [jsumners](https://github.com/jsumners) 4 | 5 | *pino-syslog* is a so called "transport" for the [pino][pino] logger. *pino-syslog* receives *pino* logs from `stdin` 6 | and transforms them into [RFC3164][rfc3164] or [RFC5424][rfc5424] (syslog) formatted messages which are written to 7 | `stdout`. The default output format is RFC5424. 8 | 9 | This transport **does not** send messages to a remote, or even local, syslog compatible server. It merely reformats the 10 | logs into syslog compatible strings. To send logs to a syslog server, use the [pino-socket][pino-socket] transport. 11 | For example: 12 | 13 | ```bash 14 | $ node your-app.js | pino-syslog | pino-socket -a syslog.example.com 15 | ``` 16 | 17 | [pino]: https://www.npmjs.com/package/pino 18 | [rfc3164]: https://tools.ietf.org/html/rfc3164 19 | [rfc5424]: https://tools.ietf.org/html/rfc5424 20 | [pino-socket]: https://www.npmjs.com/package/pino-socket 21 | 22 | ## RFC3164 23 | 24 | This RFC mandates that the maximum number of bytes that a syslog message may be 25 | is `1024`. Thus, *pino-syslog* will do one of two things when this limit is exceeded: 26 | 27 | 1. Output a JSON error log, with syslog header, that includes the original log's `time` and `level` properties, a 28 | `originalSize` property set to the number of bytes the original log message consumed, and a `msg` property set to 29 | "message exceeded syslog 1024 byte limit". 30 | 2. Truncate the message to fit within the 1024 byte limit when the `messageOnly` configuration option is set to `true`. 31 | 32 | This means you *can* lose data if your log messages are too large. If that is to be the case, you should investigate 33 | the `includeProperties` option to reduce your log size. But, really, you should investigate what it is you are logging. 34 | 35 | ## RFC5424 36 | 37 | This RFC does not limit the message size except to say that the ***receiver*** may impose a maximum. Thus, *pino-syslog* 38 | does not impose a length limit when conforming to this RFC. There are a couple of things to note, though: 39 | 40 | 1. We do not currently support the structured data portion of the log header. This section of each log is always `-`. 41 | 2. If the data to be logged includes `req.id` then it will be used as the message id portion of the log. For example, 42 | the data `{req: {id: '1234'}}` would have '1234' as the message id in the resulting formatted log. 43 | 44 | These caveats may be configurable in a later version. 45 | 46 | ## Example 47 | 48 | Given the log: 49 | 50 | ```json 51 | {"pid":94473,"hostname":"MacBook-Pro-3","level":30,"msg":"hello world","time":1459529098958,"v":1} 52 | ``` 53 | 54 | *pino-syslog* will write out: 55 | 56 | ``` 57 | <134>1 2016-04-01T16:44:58Z MacBook-Pro-3 - 94473 - - {"pid":94473,"hostname":"MacBook-Pro-3","level":30,"msg":"hello world","time":1459529098958,"v":1} 58 | ``` 59 | 60 | Or, in RFC3164 mode: 61 | 62 | ``` 63 | <134>Apr 1 16:44:58 MacBook-Pro-3 none[94473]: {"pid":94473,"hostname":"MacBook-Pro-3","level":30,"msg":"hello world","time":1459529098958,"v":1} 64 | ``` 65 | 66 | Putting it all together: 67 | 68 | ```bash 69 | $ echo '{"pid":94473,"hostname":"MacBook-Pro-3","level":30,"msg":"hello world","time":1459529098958,"v":1}' | node pino-syslog [s:0 l:8025] 70 | <134>1 2016-04-01T16:44:58Z MacBook-Pro-3 - 94473 - - {"pid":94473,"hostname":"MacBook-Pro-3","level":30,"msg":"hello world","time":1459529098958,"v":1} 71 | ``` 72 | 73 | 74 | ## Usage as Pino Transport 75 | 76 | You can use this module as a [pino transport](https://getpino.io/#/docs/transports?id=v7-transports) like so: 77 | 78 | ```js 79 | const pino = require('pino') 80 | const transport = pino.transport({ 81 | target: 'pino-syslog', 82 | level: 'info', 83 | options: { 84 | enablePipelining: false, // optional (default: true) 85 | destination: 1, // optional (default: stdout) 86 | ... // other options 87 | } 88 | }) 89 | pino(transport) 90 | ``` 91 | 92 | The options object's properties are [described below](#configuration). 93 | There is some extra properties: 94 | 95 | + `enablePipelining`: it must be set to `false` to disable the pino transport pipeline. 96 | + `destination`: it must be an integer which is used to specify the destination of the log messages. `1` is stdout, `2` is stderr and others numbers must be a file descriptor. This option is used only when the pipelining is disabled. 97 | 98 | ### Pipelining 99 | 100 | This feature is enabled by default and let you to submit the `pino-syslog` output to another destination at your choice, such as a `socket` using the `pino-socket` module: 101 | 102 | ```js 103 | const transport = pino.transport({ 104 | pipeline: [ 105 | { 106 | target: 'pino-syslog', 107 | level: 'info', 108 | options: { 109 | ... // other options 110 | } 111 | }, 112 | { 113 | target: 'pino-socket', 114 | options: { 115 | mode: 'tcp', 116 | address: '127.0.0.1', 117 | port: 8001 118 | } 119 | } 120 | ] 121 | }) 122 | pino(transport) 123 | ``` 124 | 125 | 126 | ## Usage as Pino Legacy Transport 127 | 128 | Pino supports a [legacy transport interface](https://getpino.io/#/docs/transports?id=legacy-transports) 129 | that is still supported by this module. 130 | 131 | ### Install 132 | 133 | You should install *pino-syslog* globally so that it can be used as a utility: 134 | 135 | ```bash 136 | $ npm install --production -g pino-syslog 137 | ``` 138 | 139 | ## Configuration 140 | 141 | *pino-syslog* supports configuration using option flags and/or via a JSON file. The option flags take precedence over the JSON configuration. The default options are: 142 | 143 | ```json 144 | { 145 | "modern": true, 146 | "appname": "none", 147 | "cee": false, 148 | "facility": 16, 149 | "includeProperties": [], 150 | "messageOnly": false, 151 | "tz": "UTC", 152 | "newline": false, 153 | "structuredData": "-", 154 | "sync": false 155 | } 156 | ``` 157 | 158 | This also shows the full structure of a configuration file, which can be loaded using `--config ` (`-c `). 159 | 160 | ### Option flags 161 | 162 | + `--modern` (`-m`) (boolean): indicates if RFC5424 (`true`) or RFC3164 (`false`) should be used. 163 | + `--appname` (`-a`) (string): sets the name of the application in the 'TAG' portion of the syslog header. 164 | + `--cee` (boolean): denotes whether or not to prefix the message field with `@cee: `. This will only work if 165 | `messageOnly` is `false`. 166 | + `--facility` (`-f`) (number): a valid [facility number][facility], `[0 - 23]`. 167 | + `--includeProperties` (`-p`) (array): a list of property names from the original *pino* log to include in the formatted 168 | message. This is only applicable if `messageOnly` is `false`. 169 | + `--messageOnly` (`-mo`) (boolean): indicates if the message field should contain only the `msg` property of the *pino* log, or 170 | if it should be stringified JSON. 171 | + `--tz` (string): any [valid timezone string][tzstring] that [luxon][luxon] will recognize. The timestamp field of the 172 | syslog header will be sent according to this setting. 173 | + `--newline` (`-n`) (boolean): terminate with a newline 174 | + `--structuredData` (`-s`) (string): [structured data](https://tools.ietf.org/html/rfc5424#section-6.3) to send with an RFC5424 message. 175 | + `--sync` (`-sy`) (boolean): perform writes synchronously 176 | 177 | [facility]: https://tools.ietf.org/html/rfc3164#section-4.1.1 178 | [tzstring]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones 179 | [luxon]: https://moment.github.io/luxon/#/zones?id=specifying-a-zone 180 | 181 | ### Custom levels 182 | 183 | Custom [Pino levels](https://github.com/pinojs/pino/blob/HEAD/docs/api.md#opt-customlevels) are supported. 184 | They must be established through a mapping defined under the `customLevels` 185 | configuration key. `customLevels` is a hash of hashes. Each key under 186 | `customLevels` is a custom level name with a value that is a hash with 187 | keys `level` and `syslogSeverity`. The `level` key maps to the log level number, 188 | and the `syslogSeverity` key to the name of a spec compliant syslog level: 189 | "emergency", "alert", "critical", "error", "warning", "notice", "info", or 190 | "debug". 191 | 192 | The following example shows how to add customized levels: 193 | 194 | ```json 195 | { 196 | "modern": true, 197 | "appname": "none", 198 | "customLevels": { 199 | "customLevel_name": { 200 | "level": 70, 201 | "syslogSeverity": "alert" 202 | } 203 | } 204 | } 205 | ``` 206 | 207 | ## License 208 | 209 | [MIT License](http://jsumners.mit-license.org/) 210 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict' 3 | 4 | const path = require('path') 5 | const fs = require('fs') 6 | const realPath = fs.realpathSync(__dirname) 7 | const script = path.join(realPath, 'psyslog.js') 8 | 9 | require(script.toString()) 10 | -------------------------------------------------------------------------------- /lib/rfc3164.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { DateTime } = require('luxon') 4 | const stringify = require('fast-safe-stringify') 5 | const through2 = require('through2') 6 | 7 | function messageBuilderFactory (options) { 8 | const levelToSeverity = options.pinoLevelToSyslogSeverity 9 | return function (data) { 10 | const severity = levelToSeverity.get(data.level) 11 | const pri = (8 * options.facility) + severity 12 | let timestamp = DateTime.fromMillis(data.time, { zone: options.tz }).toFormat('LLL dd HH:mm:ss') 13 | if (timestamp[4] === '0') { 14 | timestamp = timestamp.substr(0, 4) + ' ' + timestamp.substr(5) 15 | } 16 | const hostname = data.hostname.split('.')[0] 17 | const header = `<${pri}>${timestamp} ${hostname} ${options.appname}[${data.pid}]: ` 18 | const message = buildMessage(data, header.length) 19 | return (options.cee && !options.messageOnly) 20 | ? `${header}@cee: ${message}${options.newline ? '\n' : ''}` 21 | : `${header}${message}${options.newline ? '\n' : ''}` 22 | } 23 | 24 | function buildMessage (data, headerBytes) { 25 | const hlen = (options.cee) ? headerBytes - 6 : headerBytes 26 | let message = {} 27 | if (options.messageOnly) { 28 | message = data.msg.substr(0, 1024 - headerBytes) 29 | return message 30 | } 31 | 32 | if (options.includeProperties.length > 0) { 33 | options.includeProperties.forEach((p) => { message[p] = data[p] }) 34 | message = stringify(message) 35 | } else { 36 | message = stringify(data) 37 | } 38 | 39 | if (message.length > (1024 - hlen)) { 40 | message = stringify({ 41 | level: data.level, 42 | time: data.time, 43 | msg: 'message exceeded syslog 1024 byte limit', 44 | originalSize: message.length 45 | }) 46 | } 47 | 48 | return message 49 | } 50 | } 51 | 52 | module.exports = function (options) { 53 | const processMessage = messageBuilderFactory(options) 54 | return through2.obj(function transport (data, enc, cb) { 55 | const output = processMessage(data) 56 | setImmediate(() => process.stdout.write(output)) 57 | cb() 58 | }) 59 | } 60 | module.exports.messageBuilderFactory = messageBuilderFactory 61 | -------------------------------------------------------------------------------- /lib/rfc5424.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { DateTime } = require('luxon') 4 | const stringify = require('fast-safe-stringify') 5 | const through2 = require('through2') 6 | 7 | function messageBuilderFactory (options) { 8 | const version = 1 9 | const levelToSeverity = options.pinoLevelToSyslogSeverity 10 | return function (data) { 11 | const severity = levelToSeverity.get(data.level) 12 | const pri = (8 * options.facility) + severity 13 | const tstamp = DateTime.fromMillis(data.time, { zone: options.tz }) 14 | .set({ millisecond: 0 }) 15 | .toISO({ suppressMilliseconds: true }) 16 | const hostname = data.hostname 17 | const appname = (options.appname !== 'none') ? options.appname : '-' 18 | const msgid = (data.req && data.req.id) ? data.req.id : '-' 19 | const structuredData = options.structuredData || '-' 20 | const header = `<${pri}>${version} ${tstamp} ${hostname} ${appname} ${data.pid} ${msgid} ${structuredData} ` 21 | const message = buildMessage(data) 22 | return (options.cee && !options.messageOnly) 23 | ? `${header}@cee: ${message}${options.newline ? '\n' : ''}` 24 | : `${header}${message}${options.newline ? '\n' : ''}` 25 | } 26 | 27 | function buildMessage (data) { 28 | let message = {} 29 | if (options.messageOnly) { 30 | message = data.msg 31 | return message 32 | } 33 | 34 | if (options.includeProperties.length > 0) { 35 | options.includeProperties.forEach((p) => { message[p] = data[p] }) 36 | message = stringify(message) 37 | } else { 38 | message = stringify(data) 39 | } 40 | 41 | return message 42 | } 43 | } 44 | 45 | module.exports = function (options) { 46 | const processMessage = messageBuilderFactory(options) 47 | return through2.obj(function transport (data, enc, cb) { 48 | const output = processMessage(data) 49 | setImmediate(() => process.stdout.write(output)) 50 | cb() 51 | }) 52 | } 53 | 54 | module.exports.messageBuilderFactory = messageBuilderFactory 55 | -------------------------------------------------------------------------------- /lib/transport.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const build = require('pino-abstract-transport') 4 | const SonicBoom = require('sonic-boom') 5 | const { Transform, pipeline } = require('stream') 6 | const { buildOptions } = require('./utils') 7 | const { messageBuilderFactory: rfc5424 } = require('./rfc5424.js') 8 | const { messageBuilderFactory: rfc3164 } = require('./rfc3164.js') 9 | 10 | module.exports = pinoTransport 11 | 12 | function pinoTransport (options) { 13 | const opts = buildOptions(options) 14 | const processMessage = opts.modern ? rfc5424(opts) : rfc3164(opts) 15 | 16 | let destination 17 | if (opts.enablePipelining !== true) { 18 | destination = getStream(opts.destination, opts.sync) 19 | } 20 | 21 | return build(function (source) { 22 | const stream = new Transform({ 23 | objectMode: true, 24 | autoDestroy: true, 25 | transform (obj, enc, cb) { 26 | const msg = processMessage(obj) 27 | cb(null, msg) 28 | } 29 | }) 30 | 31 | const pipeflow = [ 32 | source, 33 | stream 34 | ] 35 | if (destination) { 36 | pipeflow.push(destination) 37 | } 38 | pipeline(pipeflow, () => { 39 | // process._rawDebug('pino-transport: finished piping') 40 | }) 41 | 42 | return stream 43 | }, { 44 | enablePipelining: opts.enablePipelining 45 | }) 46 | } 47 | 48 | function getStream (fileDescriptor, sync) { 49 | return new SonicBoom({ dest: parseInt(fileDescriptor), sync }) 50 | } 51 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const pinoLevels = { 4 | fatal: 60, 5 | error: 50, 6 | warn: 40, 7 | info: 30, 8 | debug: 20, 9 | trace: 10 10 | } 11 | 12 | const severity = { 13 | emergency: 0, 14 | alert: 1, 15 | critical: 2, 16 | error: 3, 17 | warning: 4, 18 | notice: 5, 19 | info: 6, 20 | debug: 7 21 | } 22 | 23 | /** 24 | * pinoLevelToSyslogSeverity is used to maintain a relationship between 25 | * known or custom pino levels and pino-syslog severity levels. 26 | * 27 | */ 28 | const pinoLevelToSyslogSeverity = new Map([ 29 | [pinoLevels.trace, severity.debug], 30 | [pinoLevels.debug, severity.debug], 31 | [pinoLevels.info, severity.info], 32 | [pinoLevels.warn, severity.warning], 33 | [pinoLevels.error, severity.error], 34 | [pinoLevels.fatal, severity.critical] 35 | ]) 36 | 37 | const defaults = { 38 | enablePipelining: true, 39 | modern: true, 40 | appname: 'none', 41 | cee: false, 42 | facility: 16, 43 | includeProperties: [], 44 | messageOnly: false, 45 | tz: 'UTC', 46 | newline: false, 47 | sync: false, 48 | pinoLevelToSyslogSeverity 49 | } 50 | 51 | /** 52 | * Applies user supplied options to override internal defaults. 53 | * When the options object contains the `customLevels` property, the custom levels are added to the `pinoLevelToSyslogSeverity` 54 | * map along with their transformation into one of the syslog levels. 55 | * 56 | * When the given severity level does not exist, the default `severity.error` is used. 57 | * 58 | * @param {*} options 59 | * @param {*} args 60 | * 61 | * @returns {object} Updated options object with any defined custom levels 62 | * added to the `pinoLevelToSyslogSeverity` field. 63 | */ 64 | function buildOptions (options = {}, args = {}) { 65 | const opts = Object.assign({}, defaults, options, args) 66 | 67 | if (options.customLevels == null) { 68 | return opts 69 | } 70 | 71 | for (const [key, { level, syslogSeverity }] of Object.entries(options.customLevels)) { 72 | const sev = severity[syslogSeverity] || severity.error 73 | 74 | pinoLevels[key] = level 75 | pinoLevelToSyslogSeverity.set(pinoLevels[key], sev) 76 | } 77 | 78 | opts.pinoLevelToSyslogSeverity = pinoLevelToSyslogSeverity 79 | return opts 80 | } 81 | 82 | module.exports = { 83 | severity, 84 | buildOptions, 85 | defaults 86 | } 87 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pino-syslog", 3 | "version": "3.2.0", 4 | "description": "A transport for pino that formats messages into syslog format", 5 | "homepage": "https://github.com/pinojs/pino-syslog", 6 | "main": "lib/transport.js", 7 | "scripts": { 8 | "test": "npm run lint && tap --no-cov 'test/**/*.test.js'", 9 | "lint": "eslint .", 10 | "lint:fix": "eslint --fix ." 11 | }, 12 | "bin": { 13 | "pino-syslog": "./app.js" 14 | }, 15 | "keywords": [ 16 | "pino", 17 | "pino-transport", 18 | "logging" 19 | ], 20 | "author": "James Sumners ", 21 | "license": "MIT", 22 | "repository": { 23 | "type": "git", 24 | "url": "https://github.com/pinojs/pino-syslog.git" 25 | }, 26 | "precommit": [ 27 | "lint", 28 | "test" 29 | ], 30 | "devDependencies": { 31 | "eslint": "^8.31.0", 32 | "eslint-config-standard": "^17.0.0", 33 | "eslint-plugin-import": "^2.26.0", 34 | "eslint-plugin-n": "^15.6.0", 35 | "eslint-plugin-node": "^11.1.0", 36 | "eslint-plugin-promise": "^6.1.1", 37 | "pino": "^8.8.0", 38 | "pino-socket": "^7.3.0", 39 | "pre-commit": "^1.2.2", 40 | "tap": "^16.3.2" 41 | }, 42 | "dependencies": { 43 | "fast-json-parse": "^1.0.2", 44 | "fast-safe-stringify": "^2.0.2", 45 | "luxon": "^3.2.1", 46 | "nopt": "^7.0.0", 47 | "pino-abstract-transport": "^1.0.0", 48 | "pump": "^3.0.0", 49 | "sonic-boom": "^3.2.1", 50 | "split2": "^4.1.0", 51 | "through2": "^4.0.2" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /psyslog.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const nopt = require('nopt') 4 | const path = require('path') 5 | const pump = require('pump') 6 | const split2 = require('split2') 7 | const parseJson = require('fast-json-parse') 8 | const { buildOptions } = require('./lib/utils') 9 | 10 | const longOpts = { 11 | modern: Boolean, 12 | appname: String, 13 | cee: Boolean, 14 | facility: Number, 15 | includeProperties: String, 16 | messageOnly: Boolean, 17 | tz: String, 18 | newline: Boolean, 19 | structuredData: String, 20 | config: String, 21 | sync: Boolean 22 | } 23 | 24 | const shortOpts = { 25 | m: '--modern', 26 | a: '--appname', 27 | f: '--facility', 28 | p: '--includeProperties', 29 | mo: '--messageOnly', 30 | n: '--newline', 31 | s: '--structuredData', 32 | c: '--config', 33 | sy: '--sync' 34 | } 35 | 36 | const args = nopt(longOpts, shortOpts) 37 | 38 | let jsonOptions = {} 39 | if (args.config) { 40 | try { 41 | jsonOptions = require(path.resolve(args.config)) 42 | } catch (e) { 43 | process.stderr.write(`could not load settings file, using defaults: ${e.message}`) 44 | } 45 | } 46 | 47 | const options = buildOptions(jsonOptions, args) 48 | 49 | let myTransport 50 | if (options.modern) { 51 | myTransport = require('./lib/rfc5424.js')(options) 52 | } else { 53 | myTransport = require('./lib/rfc3164.js')(options) 54 | } 55 | 56 | function parser (str) { 57 | const result = parseJson(str) 58 | if (result.err) return 59 | return result.value 60 | } 61 | 62 | pump(process.stdin, split2(parser), myTransport) 63 | process.on('SIGINT', () => { process.exit(0) }) 64 | -------------------------------------------------------------------------------- /test/3164.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | const spawn = require('child_process').spawn 5 | const test = require('tap').test 6 | const { join } = path 7 | const os = require('os') 8 | const fs = require('fs') 9 | const pino = require('pino') 10 | const { once } = require('events') 11 | 12 | const { promisify } = require('util') 13 | const timeout = promisify(setTimeout) 14 | 15 | const messages = require(path.join(__dirname, 'fixtures', 'messages')) 16 | const psyslogPath = path.join(path.resolve(__dirname, '..', 'psyslog')) 17 | 18 | function configPath () { 19 | return path.join.apply(null, [__dirname, 'fixtures', 'configs'].concat(Array.from(arguments))) 20 | } 21 | 22 | test('skips non-json input', (t) => { 23 | t.plan(1) 24 | const psyslog = spawn('node', [psyslogPath]) 25 | 26 | psyslog.stdout.on('data', (data) => { 27 | t.fail('should not receive any data') 28 | }) 29 | 30 | psyslog.on('close', (code) => { 31 | t.equal(code, 0) 32 | }) 33 | 34 | psyslog.stdin.end('this is not json\n') 35 | }) 36 | 37 | test('returns error for overly long rfc3164 messages', (t) => { 38 | t.plan(1) 39 | const expected = '<134>Apr 1 16:44:58 MacBook-Pro-3 none[94473]: @cee: {"level":30,"time":1459529098958,"msg":"message exceeded syslog 1024 byte limit","originalSize":1110}' 40 | const psyslog = spawn('node', [psyslogPath, '-c', configPath('3164', 'cee.json')]) 41 | 42 | psyslog.stdout.on('data', (data) => { 43 | const msg = data.toString() 44 | t.equal(msg, expected) 45 | psyslog.kill() 46 | }) 47 | 48 | psyslog.stdin.write(messages.stupidLong + '\n') 49 | }) 50 | 51 | test('formats to message only', (t) => { 52 | t.plan(1) 53 | const expected = '<134>Apr 1 16:44:58 MacBook-Pro-3 none[94473]: hello world' 54 | const psyslog = spawn('node', [psyslogPath, '-c', configPath('3164', 'messageOnly.json')]) 55 | 56 | psyslog.stdout.on('data', (data) => { 57 | const msg = data.toString() 58 | t.equal(msg, expected) 59 | psyslog.kill() 60 | }) 61 | 62 | psyslog.stdin.write(messages.helloWorld + '\n') 63 | }) 64 | 65 | test('sets application name', (t) => { 66 | t.plan(1) 67 | const expected = '<134>Apr 1 16:44:58 MacBook-Pro-3 test[94473]: hello world' 68 | const psyslog = spawn('node', [psyslogPath, '-c', configPath('3164', 'appname.json')]) 69 | 70 | psyslog.stdout.on('data', (data) => { 71 | const msg = data.toString() 72 | t.equal(msg, expected) 73 | psyslog.kill() 74 | }) 75 | 76 | psyslog.stdin.write(messages.helloWorld + '\n') 77 | }) 78 | 79 | test('sets facility', (t) => { 80 | t.plan(1) 81 | const expected = '<6>Apr 1 16:44:58 MacBook-Pro-3 none[94473]: hello world' 82 | const psyslog = spawn('node', [psyslogPath, '-c', configPath('3164', 'facility.json')]) 83 | 84 | psyslog.stdout.on('data', (data) => { 85 | const msg = data.toString() 86 | t.equal(msg, expected) 87 | psyslog.kill() 88 | }) 89 | 90 | psyslog.stdin.write(messages.helloWorld + '\n') 91 | }) 92 | 93 | test('format timestamp with leading zero in days', (t) => { 94 | t.plan(1) 95 | const expected = '<134>Feb 3 02:20:00 MacBook-Pro-3 none[94473]: hello world' 96 | const psyslog = spawn('node', [psyslogPath, '-c', configPath('3164', 'date.json')]) 97 | 98 | psyslog.stdout.on('data', (data) => { 99 | const msg = data.toString() 100 | t.equal(msg, expected) 101 | psyslog.kill() 102 | }) 103 | 104 | psyslog.stdin.write(messages.leadingDay + '\n') 105 | }) 106 | 107 | test('format timestamp with trailing zero in days', (t) => { 108 | t.plan(1) 109 | const expected = '<134>Feb 10 02:20:00 MacBook-Pro-3 none[94473]: hello world' 110 | const psyslog = spawn('node', [psyslogPath, '-c', configPath('3164', 'date.json')]) 111 | 112 | psyslog.stdout.on('data', (data) => { 113 | const msg = data.toString() 114 | t.equal(msg, expected) 115 | psyslog.kill() 116 | }) 117 | 118 | psyslog.stdin.write(messages.trailingDay + '\n') 119 | }) 120 | 121 | test('sets timezone', (t) => { 122 | t.plan(1) 123 | const expected = '<134>Apr 1 12:44:58 MacBook-Pro-3 none[94473]: hello world' 124 | const psyslog = spawn('node', [psyslogPath, '-c', configPath('3164', 'tz.json')]) 125 | 126 | psyslog.stdout.on('data', (data) => { 127 | const msg = data.toString() 128 | t.equal(msg, expected) 129 | psyslog.kill() 130 | }) 131 | 132 | psyslog.stdin.write(messages.helloWorld + '\n') 133 | }) 134 | 135 | test('prepends `@cee `', (t) => { 136 | t.plan(1) 137 | const header = '<134>Apr 1 16:44:58 MacBook-Pro-3 none[94473]: @cee: ' 138 | const psyslog = spawn('node', [psyslogPath, '-c', configPath('3164', 'cee.json')]) 139 | 140 | psyslog.stdout.on('data', (data) => { 141 | const msg = data.toString() 142 | t.equal(msg, header + messages.helloWorld) 143 | psyslog.kill() 144 | }) 145 | 146 | psyslog.stdin.write(messages.helloWorld + '\n') 147 | }) 148 | 149 | test('does not prepend `@cee ` for non-json messages', (t) => { 150 | t.plan(1) 151 | const expected = '<134>Apr 1 16:44:58 MacBook-Pro-3 none[94473]: hello world' 152 | const psyslog = spawn('node', [psyslogPath, '-c', configPath('3164', 'ceeMessageOnly.json')]) 153 | 154 | psyslog.stdout.on('data', (data) => { 155 | const msg = data.toString() 156 | t.equal(msg, expected) 157 | psyslog.kill() 158 | }) 159 | 160 | psyslog.stdin.write(messages.helloWorld + '\n') 161 | }) 162 | 163 | test('truncates overly long message only log', (t) => { 164 | t.plan(1) 165 | const expected = '<134>Apr 1 16:44:58 MacBook-Pro-3 none[94473]: ' + JSON.parse(messages.stupidLong).msg.substr(0, 976) 166 | const psyslog = spawn('node', [psyslogPath, '-c', configPath('3164', 'messageOnly.json')]) 167 | 168 | psyslog.stdout.on('data', (data) => { 169 | const msg = data.toString() 170 | t.equal(msg, expected) 171 | psyslog.kill() 172 | }) 173 | 174 | psyslog.stdin.write(messages.stupidLong + '\n') 175 | }) 176 | 177 | test('appends newline', (t) => { 178 | t.plan(1) 179 | const expected = '<134>Apr 1 16:44:58 MacBook-Pro-3 none[94473]: ' + messages.helloWorld + '\n' 180 | const psyslog = spawn('node', [psyslogPath, '-c', configPath('3164', 'newline.json')]) 181 | 182 | psyslog.stdout.on('data', (data) => { 183 | const msg = data.toString() 184 | t.equal(msg, expected) 185 | psyslog.kill() 186 | }) 187 | 188 | psyslog.stdin.write(messages.helloWorld + '\n') 189 | }) 190 | 191 | test('write synchronously', (t) => { 192 | t.plan(1) 193 | const expected = '<134>Apr 1 16:44:58 MacBook-Pro-3 none[94473]: ' + messages.helloWorld 194 | const psyslog = spawn('node', [psyslogPath, '-c', configPath('3164', 'sync.json')]) 195 | 196 | psyslog.stdout.on('data', (data) => { 197 | const msg = data.toString() 198 | t.equal(msg, expected) 199 | psyslog.kill() 200 | }) 201 | 202 | psyslog.stdin.write(messages.helloWorld + '\n') 203 | }) 204 | 205 | test('sets customLevels', (t) => { 206 | t.plan(1) 207 | const expected = '<134>Apr 1 16:44:58 MacBook-Pro-3 test[94473]: hello world' 208 | const psyslog = spawn('node', [psyslogPath, '-c', configPath('3164', 'appname.json')]) 209 | 210 | psyslog.stdout.on('data', (data) => { 211 | const msg = data.toString() 212 | t.equal(msg, expected) 213 | psyslog.kill() 214 | }) 215 | 216 | psyslog.stdin.write(messages.helloWorld + '\n') 217 | }) 218 | 219 | function getConfigPath () { 220 | const cpath = join.apply(null, [__dirname, 'fixtures', 'configs'].concat(Array.from(arguments))) 221 | return require(cpath) 222 | } 223 | 224 | const pinoSyslog = join(__dirname, '..', 'lib', 'transport.js') 225 | 226 | test('syslog pino transport test rfc3164', { only: true }, async t => { 227 | const destination = join(os.tmpdir(), 'pino-transport-test.log') 228 | 229 | const fd = fs.openSync(destination, 'w+') 230 | const sysLogOptions = { 231 | destination: fd, 232 | enablePipelining: false, 233 | ...getConfigPath('3164', 'newline.json') 234 | } 235 | 236 | const transport = pino.transport({ 237 | target: pinoSyslog, 238 | level: 'info', 239 | options: sysLogOptions 240 | }) 241 | const log = pino(transport) 242 | t.pass('built pino') 243 | await once(transport, 'ready') 244 | t.pass('transport ready ' + destination) 245 | 246 | log.info(JSON.parse(messages.leadingDay)) 247 | log.debug(JSON.parse(messages.helloWorld)) // it is skipped 248 | log.info(JSON.parse(messages.trailingDay)) 249 | 250 | await timeout(1000) 251 | 252 | const data = fs.readFileSync(destination, 'utf8').trim().split('\n') 253 | t.ok(data[0].startsWith('<134>Feb 3 01:20:00 MacBook-Pro-3 none[94473]: '), true, 'first line leadingDay') 254 | t.ok(data[1].startsWith('<134>Feb 10 01:20:00 MacBook-Pro-3 none[94473]: '), true, 'first line trailingDay') 255 | }) 256 | -------------------------------------------------------------------------------- /test/5424.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | const spawn = require('child_process').spawn 5 | const test = require('tap').test 6 | const os = require('os') 7 | const fs = require('fs') 8 | const pino = require('pino') 9 | const { once } = require('events') 10 | 11 | const join = path.join 12 | const { promisify } = require('util') 13 | const timeout = promisify(setTimeout) 14 | 15 | const messages = require(path.join(__dirname, 'fixtures', 'messages')) 16 | const psyslogPath = path.join(path.resolve(__dirname, '..', 'psyslog')) 17 | 18 | function configPath () { 19 | return path.join.apply(null, [__dirname, 'fixtures', 'configs'].concat(Array.from(arguments))) 20 | } 21 | 22 | test('skips non-json input', (t) => { 23 | t.plan(1) 24 | const psyslog = spawn('node', [psyslogPath]) 25 | 26 | psyslog.stdout.on('data', (data) => { 27 | t.fail('should not receive any data') 28 | }) 29 | 30 | psyslog.on('close', (code) => { 31 | t.equal(code, 0) 32 | }) 33 | 34 | psyslog.stdin.end('this is not json\n') 35 | }) 36 | 37 | test('hello world', (t) => { 38 | t.plan(1) 39 | const header = '<134>1 2016-04-01T16:44:58Z MacBook-Pro-3 - 94473 - - ' 40 | const psyslog = spawn('node', [psyslogPath]) 41 | 42 | psyslog.stdout.on('data', (data) => { 43 | const msg = data.toString() 44 | t.equal(msg, header + messages.helloWorld) 45 | psyslog.kill() 46 | }) 47 | 48 | psyslog.stdin.write(messages.helloWorld + '\n') 49 | }) 50 | 51 | test('formats to message only', (t) => { 52 | t.plan(1) 53 | const expected = '<134>1 2016-04-01T16:44:58Z MacBook-Pro-3 - 94473 - - hello world' 54 | const psyslog = spawn('node', [psyslogPath, '-c', configPath('5424', 'messageOnly.json')]) 55 | 56 | psyslog.stdout.on('data', (data) => { 57 | const msg = data.toString() 58 | t.equal(msg, expected) 59 | psyslog.kill() 60 | }) 61 | 62 | psyslog.stdin.write(messages.helloWorld + '\n') 63 | }) 64 | 65 | test('sets application name', (t) => { 66 | t.plan(1) 67 | const expected = '<134>1 2016-04-01T16:44:58Z MacBook-Pro-3 test 94473 - - hello world' 68 | const psyslog = spawn('node', [psyslogPath, '-c', configPath('5424', 'appname.json')]) 69 | 70 | psyslog.stdout.on('data', (data) => { 71 | const msg = data.toString() 72 | t.equal(msg, expected) 73 | psyslog.kill() 74 | }) 75 | 76 | psyslog.stdin.write(messages.helloWorld + '\n') 77 | }) 78 | 79 | test('sets facility', (t) => { 80 | t.plan(1) 81 | const expected = '<6>1 2016-04-01T16:44:58Z MacBook-Pro-3 - 94473 - - hello world' 82 | const psyslog = spawn('node', [psyslogPath, '-c', configPath('5424', 'facility.json')]) 83 | 84 | psyslog.stdout.on('data', (data) => { 85 | const msg = data.toString() 86 | t.equal(msg, expected) 87 | psyslog.kill() 88 | }) 89 | 90 | psyslog.stdin.write(messages.helloWorld + '\n') 91 | }) 92 | 93 | test('sets timezone', (t) => { 94 | t.plan(1) 95 | const expected = '<134>1 2016-04-01T12:44:58-04:00 MacBook-Pro-3 - 94473 - - hello world' 96 | const psyslog = spawn('node', [psyslogPath, '-c', configPath('5424', 'tz.json')]) 97 | 98 | psyslog.stdout.on('data', (data) => { 99 | const msg = data.toString() 100 | t.equal(msg, expected) 101 | psyslog.kill() 102 | }) 103 | 104 | psyslog.stdin.write(messages.helloWorld + '\n') 105 | }) 106 | 107 | test('prepends `@cee `', (t) => { 108 | t.plan(1) 109 | const header = '<134>1 2016-04-01T16:44:58Z MacBook-Pro-3 - 94473 - - @cee: ' 110 | const psyslog = spawn('node', [psyslogPath, '-c', configPath('5424', 'cee.json')]) 111 | 112 | psyslog.stdout.on('data', (data) => { 113 | const msg = data.toString() 114 | t.equal(msg, header + messages.helloWorld) 115 | psyslog.kill() 116 | }) 117 | 118 | psyslog.stdin.write(messages.helloWorld + '\n') 119 | }) 120 | 121 | test('does not prepend `@cee ` for non-json messages', (t) => { 122 | t.plan(1) 123 | const expected = '<134>1 2016-04-01T16:44:58Z MacBook-Pro-3 - 94473 - - hello world' 124 | const psyslog = spawn('node', [psyslogPath, '-c', configPath('5424', 'ceeMessageOnly.json')]) 125 | 126 | psyslog.stdout.on('data', (data) => { 127 | const msg = data.toString() 128 | t.equal(msg, expected) 129 | psyslog.kill() 130 | }) 131 | 132 | psyslog.stdin.write(messages.helloWorld + '\n') 133 | }) 134 | 135 | test('appends newline', (t) => { 136 | t.plan(1) 137 | const expected = '<134>1 2016-04-01T16:44:58Z MacBook-Pro-3 - 94473 - - ' + messages.helloWorld + '\n' 138 | const psyslog = spawn('node', [psyslogPath, '-c', configPath('5424', 'newline.json')]) 139 | 140 | psyslog.stdout.on('data', (data) => { 141 | const msg = data.toString() 142 | t.equal(msg, expected) 143 | psyslog.kill() 144 | }) 145 | 146 | psyslog.stdin.write(messages.helloWorld + '\n') 147 | }) 148 | 149 | test('write synchronously', (t) => { 150 | t.plan(1) 151 | const expected = '<134>1 2016-04-01T16:44:58Z MacBook-Pro-3 - 94473 - - ' + messages.helloWorld 152 | const psyslog = spawn('node', [psyslogPath, '-c', configPath('5424', 'sync.json')]) 153 | 154 | psyslog.stdout.on('data', (data) => { 155 | const msg = data.toString() 156 | t.equal(msg, expected) 157 | psyslog.kill() 158 | }) 159 | 160 | psyslog.stdin.write(messages.helloWorld + '\n') 161 | }) 162 | 163 | test('uses structured data', (t) => { 164 | t.plan(1) 165 | const expected = '<134>1 2016-04-01T16:44:58Z MacBook-Pro-3 - 94473 - [a@b x="y"] ' + messages.helloWorld 166 | const psyslog = spawn('node', [psyslogPath, '-c', configPath('5424', 'structuredData.json')]) 167 | 168 | psyslog.stdout.on('data', (data) => { 169 | const msg = data.toString() 170 | t.equal(msg, expected) 171 | psyslog.kill() 172 | }) 173 | 174 | psyslog.stdin.write(messages.helloWorld + '\n') 175 | }) 176 | 177 | test('sets customLevels', (t) => { 178 | t.plan(1) 179 | const expected = '<134>1 2016-04-01T16:44:58Z MacBook-Pro-3 test 94473 - - hello world' 180 | const psyslog = spawn('node', [psyslogPath, '-c', configPath('5424', 'custom-level.json')]) 181 | 182 | psyslog.stdout.on('data', (data) => { 183 | const msg = data.toString() 184 | t.equal(msg, expected) 185 | psyslog.kill() 186 | }) 187 | 188 | psyslog.stdin.write(messages.helloWorld + '\n') 189 | }) 190 | 191 | function getConfigPath () { 192 | const cpath = join.apply(null, [__dirname, 'fixtures', 'configs'].concat(Array.from(arguments))) 193 | return require(cpath) 194 | } 195 | 196 | const pinoSyslog = join(__dirname, '..', 'lib', 'transport.js') 197 | 198 | test('syslog pino transport test rfc5424', async t => { 199 | const destination = join(os.tmpdir(), 'pino-transport-test.log') 200 | 201 | const fd = fs.openSync(destination, 'w+') 202 | const sysLogOptions = { 203 | destination: fd, 204 | enablePipelining: false, 205 | ...getConfigPath('5424', 'newline.json') 206 | } 207 | 208 | const transport = pino.transport({ 209 | target: pinoSyslog, 210 | level: 'info', 211 | options: sysLogOptions 212 | }) 213 | const log = pino(transport) 214 | t.pass('built pino') 215 | await once(transport, 'ready') 216 | t.pass('transport ready ' + destination) 217 | 218 | log.info(JSON.parse(messages.leadingDay)) 219 | log.debug(JSON.parse(messages.helloWorld)) // it is skipped 220 | log.info(JSON.parse(messages.trailingDay)) 221 | 222 | await timeout(1000) 223 | 224 | const data = fs.readFileSync(destination, 'utf8').trim().split('\n') 225 | t.ok(data[0].startsWith('<134>1 2018-02-03T01:20:00Z MacBook-Pro-3 - 94473 - - '), 'first line leadingDay') 226 | t.ok(data[1].startsWith('<134>1 2018-02-10T01:20:00Z MacBook-Pro-3 - 94473 - - '), 'first line trailingDay') 227 | }) 228 | -------------------------------------------------------------------------------- /test/fixtures/configs/3164/appname.json: -------------------------------------------------------------------------------- 1 | { 2 | "modern": false, 3 | "appname": "test", 4 | "messageOnly": true 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/configs/3164/cee.json: -------------------------------------------------------------------------------- 1 | { 2 | "modern": false, 3 | "cee": true 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/configs/3164/ceeMessageOnly.json: -------------------------------------------------------------------------------- 1 | { 2 | "modern": false, 3 | "cee": true, 4 | "messageOnly": true 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/configs/3164/custom-level.json: -------------------------------------------------------------------------------- 1 | { 2 | "modern": false, 3 | "appname": "test", 4 | "messageOnly": true, 5 | "customLevels": { 6 | "unexpectedCodeBranch": { "level": 70, "syslogSeverity": "alert"} 7 | } 8 | } -------------------------------------------------------------------------------- /test/fixtures/configs/3164/date.json: -------------------------------------------------------------------------------- 1 | { 2 | "modern": false, 3 | "tz": "Europe/Zurich", 4 | "messageOnly": true 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/configs/3164/facility.json: -------------------------------------------------------------------------------- 1 | { 2 | "modern": false, 3 | "facility": 0, 4 | "messageOnly": true 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/configs/3164/messageOnly.json: -------------------------------------------------------------------------------- 1 | { 2 | "modern": false, 3 | "messageOnly": true 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/configs/3164/newline.json: -------------------------------------------------------------------------------- 1 | { 2 | "modern": false, 3 | "newline": true 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/configs/3164/sync.json: -------------------------------------------------------------------------------- 1 | { 2 | "modern": false, 3 | "sync": true 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/configs/3164/tz.json: -------------------------------------------------------------------------------- 1 | { 2 | "modern": false, 3 | "tz": "America/New_York", 4 | "messageOnly": true 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/configs/5424/appname.json: -------------------------------------------------------------------------------- 1 | { 2 | "appname": "test", 3 | "messageOnly": true 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/configs/5424/cee.json: -------------------------------------------------------------------------------- 1 | { 2 | "cee": true 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/configs/5424/ceeMessageOnly.json: -------------------------------------------------------------------------------- 1 | { 2 | "cee": true, 3 | "messageOnly": true 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/configs/5424/custom-level.json: -------------------------------------------------------------------------------- 1 | { 2 | "appname": "test", 3 | "messageOnly": true, 4 | "customLevels": { 5 | "unexpectedCodeBranch": { "level": 70, "syslogSeverity": "alert"} 6 | } 7 | } -------------------------------------------------------------------------------- /test/fixtures/configs/5424/facility.json: -------------------------------------------------------------------------------- 1 | { 2 | "facility": 0, 3 | "messageOnly": true 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/configs/5424/messageOnly.json: -------------------------------------------------------------------------------- 1 | { 2 | "messageOnly": true 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/configs/5424/newline.json: -------------------------------------------------------------------------------- 1 | { 2 | "newline": true 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/configs/5424/structuredData.json: -------------------------------------------------------------------------------- 1 | { 2 | "structuredData": "[a@b x=\"y\"]" 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/configs/5424/sync.json: -------------------------------------------------------------------------------- 1 | { 2 | "sync": true 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/configs/5424/tz.json: -------------------------------------------------------------------------------- 1 | { 2 | "tz": "America/New_York", 3 | "messageOnly": true 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/log-stdout.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const pino = require('pino') 4 | const { join } = require('path') 5 | 6 | const options = { 7 | destination: parseInt(process.argv[2]), 8 | enablePipelining: false, 9 | cee: true, 10 | messageOnly: true 11 | } 12 | 13 | const transport = pino.transport({ 14 | target: join(__dirname, '../../lib/transport.js'), 15 | level: 'info', 16 | options 17 | }) 18 | 19 | const log = pino(transport) 20 | const logString = require('./messages').helloWorld 21 | log.info(JSON.parse(logString)) 22 | -------------------------------------------------------------------------------- /test/fixtures/messages.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = { 4 | helloWorld: '{"pid":94473,"hostname":"MacBook-Pro-3","level":30,"msg":"hello world","time":1459529098958,"v":1}', 5 | leadingDay: '{"pid":94473,"hostname":"MacBook-Pro-3","level":30,"msg":"hello world","time":1517620800000,"v":1}', 6 | trailingDay: '{"pid":94473,"hostname":"MacBook-Pro-3","level":30,"msg":"hello world","time":1518225600000,"v":1}', 7 | stupidLong: '{"pid":94473,"hostname":"MacBook-Pro-3","level":30,"msg":"Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at","time":1459529098958,"v":1}' 8 | } 9 | -------------------------------------------------------------------------------- /test/pipeline.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const os = require('os') 4 | const { join } = require('path') 5 | const { once } = require('events') 6 | const { test } = require('tap') 7 | const { createTcpListener } = require('pino-socket/test/utils') 8 | const pino = require('pino') 9 | 10 | const pinoSyslog = join(__dirname, '..', 'lib', 'transport.js') 11 | 12 | const messages = require(join(__dirname, 'fixtures', 'messages')) 13 | 14 | function getConfigPath () { 15 | const cpath = join.apply(null, [__dirname, 'fixtures', 'configs'].concat(Array.from(arguments))) 16 | return require(cpath) 17 | } 18 | 19 | test('pino pipeline', t => { 20 | t.plan(4) 21 | const destination = join(os.tmpdir(), 'pino-transport-test.log') 22 | 23 | const expected = [ 24 | '<134>1 2018-02-03T01:20:00Z MacBook-Pro-3 - 94473 - - ', 25 | '<134>1 2018-02-10T01:20:00Z MacBook-Pro-3 - 94473 - - ' 26 | ] 27 | 28 | createTcpListener(msg => { 29 | msg.split('\n') 30 | .filter(line => line) // skip empty lines 31 | .forEach(line => { 32 | t.ok(line.startsWith(expected.shift())) 33 | }) 34 | }).then((serverSocket) => { 35 | t.teardown(() => { 36 | serverSocket.close() 37 | serverSocket.unref() 38 | transport.end() 39 | }) 40 | 41 | const address = serverSocket.address().address 42 | const port = serverSocket.address().port 43 | 44 | const transport = pino.transport({ 45 | pipeline: [ 46 | { 47 | target: pinoSyslog, 48 | level: 'info', 49 | options: { 50 | ...getConfigPath('5424', 'newline.json') 51 | } 52 | }, 53 | { 54 | target: 'pino-socket', 55 | options: { 56 | mode: 'tcp', 57 | address, 58 | port 59 | } 60 | } 61 | ] 62 | }) 63 | const log = pino(transport) 64 | t.pass('built pino') 65 | return once(transport, 'ready').then(() => log) 66 | }).then(log => { 67 | t.pass('transport ready ' + destination) 68 | 69 | log.info(JSON.parse(messages.leadingDay)) 70 | log.debug(JSON.parse(messages.helloWorld)) // it is skipped 71 | log.info(JSON.parse(messages.trailingDay)) 72 | }) 73 | .catch((err) => { 74 | t.fail(err) 75 | }) 76 | }) 77 | -------------------------------------------------------------------------------- /test/transport.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { join } = require('path') 4 | const { spawnSync } = require('child_process') 5 | const { test } = require('tap') 6 | 7 | test('syslog pino transport test stdout', async t => { 8 | const result = spawnSync('node', ['--no-warnings', join(__dirname, 'fixtures', 'log-stdout.js'), '1'], { 9 | cwd: process.cwd() 10 | }) 11 | t.equal(result.stdout.toString().trim(), '<134>1 2016-04-01T16:44:58Z MacBook-Pro-3 - 94473 - - hello world') 12 | t.equal(result.status, 0) 13 | }) 14 | 15 | test('syslog pino transport test stderr', async t => { 16 | const result = spawnSync('node', ['--no-warnings', join(__dirname, 'fixtures', 'log-stdout.js'), '2'], { 17 | cwd: process.cwd() 18 | }) 19 | t.equal(result.stderr.toString().trim(), '<134>1 2016-04-01T16:44:58Z MacBook-Pro-3 - 94473 - - hello world') 20 | t.equal(result.status, 0) 21 | }) 22 | --------------------------------------------------------------------------------