Skip to content

Commit

Permalink
fix: ensure mesages are always strings
Browse files Browse the repository at this point in the history
License: MIT
Signed-off-by: Oli Evans <oli@protocol.ai>
  • Loading branch information
olizilla committed Jul 25, 2023
1 parent 04f9302 commit d28fe58
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 20 deletions.
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,22 @@

Post logs to loki from a Cloudflare [Tail Worker].

It will convert your logs to JSON and send the to `env.LOKI_URL`
![tail worker diagram](https://developers.cloudflare.com/assets/tail-workers_hu70389a70db4cc3aeffe45d478af42800_41597_1280x532_resize_q75_box_3-1cd0ba86.png)

Converts your logs and errors to JSON and POSTs them to `env.LOKI_URL`.

By using a Tail Worker we ship logs to Loki even in cases where the primary worker hits sub-request limits.

A single instance of this worker on your account can ship all your logs to a shared loki instance.

Each workers log stream is identified by a loki label `worker: 'your-worker-name'`.

## Getting started

Deploy this worker to your account and set the secrets using `wrangler secret put`

- LOKI_URL - the url to post to e.g `https://eg.grafana.net/loki/api/v1/push`
- LOKI_TOKEN - basic auth token for loki
- LOKI_TOKEN - basic auth token for loki: base64 encoded `user:pass` string

Configure this worker as a `tail_consumer` in any other workers whose logs you want to send to Loki.

Expand All @@ -22,5 +28,4 @@ tail_consumers = [
]
```


[Tail Worker]: https://developers.cloudflare.com/workers/observability/tail-workers/
49 changes: 35 additions & 14 deletions src/worker.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
/**
* @typedef {object} Env
* @prop {string} LOKI_URL - full url to post logs to
* @prop {string} LOKI_TOKEN - basic auth token for loki url
* @prop {string} DEBUG - log every request
*/

/**
* POST tail events to Loki
*
* @see https://developers.cloudflare.com/workers/runtime-apis/tail-event/
* @see https://developers.cloudflare.com/workers/observability/tail-workers/
*
* @type {ExportedHandler<Env>}
* @typedef {object} Env
* @prop {string} LOKI_URL - full url to post logs to
* @prop {string} LOKI_TOKEN - basic auth token for loki url
*/
export default {
async tail (events, env) {
const lokiBody = {
streams: events.map(toLoki)
}
if (env.DEBUG === 'true') {
console.log('POST', env.LOKI_URL, JSON.stringify(lokiBody))
}
const res = await fetch(env.LOKI_URL, {
method: 'POST',
headers: {
Expand All @@ -23,9 +30,13 @@ export default {
},
body: JSON.stringify(lokiBody)
})
if (!res.ok) {
console.error(`${res.status} ${res.statusText} ${res.url}`)
console.log(await res.text())
if (!res.ok || env.DEBUG === 'true') {
console.log(`${res.status} ${res.statusText} ${res.url}`)
const text = await res.text()
console.log(text)
if (!res.ok) {
throw new Error(`Failed to POST to loki ${res.status} ${res.url} ${text}`)
}
}
}
}
Expand All @@ -37,6 +48,7 @@ export function toLoki (tailItem) {
const req = formatRequest(tailItem)
const logs = tailItem.logs.map(formatLog)
const errs = tailItem.exceptions.map(formatException)
/** @type [string, string][] */
const values = [...logs, ...errs]
if (req !== undefined) {
values.unshift(req)
Expand All @@ -52,30 +64,39 @@ export function toLoki (tailItem) {

/** @param {number} ms epoch time in milliseconds */
export function toNano (ms) {
const nano = BigInt(ms) * BigInt(10_000)
const nano = BigInt(ms) * BigInt(1_000_000)
return nano.toString()
}

/** @param {TraceItem} item */
/**
* @param {TraceItem} item
* @returns {[string, string] | undefined}
**/
export function formatRequest ({ eventTimestamp, event }) {
// @ts-expect-error checking for request here
const request = event?.request
if (eventTimestamp && request) {
const { url, method, headers, cf } = request
return [toNano(eventTimestamp), { url, method, headers, cf, level: 'request' }]
return [toNano(eventTimestamp), JSON.stringify({ url, method, headers, cf, level: 'request' })]
}
}

/** @param {TraceLog} log */
/**
* @param {TraceLog} log
* @returns {[string, string]}
**/
export function formatLog ({ timestamp, message, level }) {
const [first, ...args] = message
if (typeof first === 'object') {
return [toNano(timestamp), { ...first, args, level }]
return [toNano(timestamp), JSON.stringify({ ...first, args, level })]
}
return [toNano(timestamp), { msg: first, args, level }]
return [toNano(timestamp), JSON.stringify({ msg: first, args, level })]
}

/** @param {TraceException} e */
/**
* @param {TraceException} e
* @returns {[string, string]}
**/
export function formatException ({ timestamp, name, message }) {
return [toNano(timestamp), { msg: message, name, level: 'fatal' }]
return [toNano(timestamp), JSON.stringify({ msg: message, name, level: 'fatal' })]
}
7 changes: 4 additions & 3 deletions test/worker.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ test('toLoki', t => {
})
t.is(values.length, 3)

t.is(values[0][0], '15870586420050000')
t.is(values[0][0].length, 19, 'should be nanoseconds since epoch')
t.is(values[0][0], '1587058642005000000')
t.like(values[0][1], {
url: 'https://example.com/some/requested/url',
method: 'GET',
Expand All @@ -22,14 +23,14 @@ test('toLoki', t => {
level: 'request'
})

t.is(values[1][0], '15870586420060000')
t.is(values[1][0], '1587058642006000000')
t.like(values[1][1], {
msg: 'string passed to console.log()',
args: [99, { woo: 'haa' }],
level: 'log'
})

t.is(values[2][0], '15870586420070000')
t.is(values[2][0], '1587058642007000000')
t.like(values[2][1], {
msg: 'Threw a sample exception',
name: 'Error',
Expand Down
2 changes: 2 additions & 0 deletions wrangler.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ compatibility_date = "2023-07-17"

[vars]
ENV = "dev"
DEBUG = "true"

# STAGING!
[env.staging]
account_id = "fffa4b4363a7e5250af8357087263b3a"

[env.staging.vars]
ENV = "staging"
DEBUG = "true"

# PROD!
[env.productuion]
Expand Down

0 comments on commit d28fe58

Please sign in to comment.