Welcome to cloki, an open-source logger designed to bridge Cloudflare Workers with Grafana Cloud's Loki seamlessly and efficiently. Targeted at individual developers, cloki aims to reduce maintenance costs while offering a straightforward logging solution. With minimal configuration and the sole use of the fetch API, cloki is an easy-to-implement tool for effective logging.
- Easy Integration: Connect Cloudflare Workers with Grafana Cloud's Loki effortlessly.
- Minimal Configuration: Get started with just a few simple settings.
- Zero Dependencies: cloki has zero dependencies, making it easy to maintain.
- Fetch API Usage: cloki uses the Fetch API, so it can be used in any environment like edge runtime.
$ npm i @miketako3/cloki- Create a Grafana Cloud account from here.
- Access https://grafana.com/orgs/${YOUR_ORG_NAME}
- Click Detail.

- You got the required information.

import {Cloki} from '@miketako3/cloki'
const logger = getLokiLogger({
lokiHost: "Host URL (e.g. logs-xxx-yyy.grafana.net)",
lokiUser: "User (e.g. 123456)",
lokiToken: "Generated API Token"
});
await logger.info({message: "Hello World!"});
await logger.error({message: "Hello World!", error: error});
// with addional labels
await logger.info({message: "Hello World!"}, {foo: "bar"});You can set default labels that will be added to every log, and specify a minimum log level.
const logger = getLokiLogger({
lokiHost: "...",
lokiUser: "...",
lokiToken: "...",
defaultLabels: { env: "production", app: "my-service" },
minLevel: "info" // 'debug' | 'info' | 'warn' | 'error'
});You can pass a string directly as a message. It will be converted to {"message": "your string"}.
await logger.info("Hello World!");To prevent the log sending from being cancelled when the worker returns a response, you can pass the ExecutionContext to getLokiLogger or to each log method. You can also automatically add labels from request.cf.
export default {
async fetch(request, env, ctx) {
const logger = getLokiLogger({
cf: request.cf, // Automatically add cf_colo, cf_country, etc.
ctx: ctx // Use ctx.waitUntil internally for all logs
});
// This will use ctx.waitUntil internally and won't block the response
logger.info("Request received", { path: new URL(request.url).pathname });
return new Response("OK");
}
}If you set the following environment variables, you can initialize the logger without any arguments:
LOKI_HOST(e.g.,logs-prod-us-central1.grafana.net)LOKI_USERLOKI_TOKEN
Standard usage for Cloudflare Workers:
export default {
async fetch(request, env, ctx) {
const logger = getLokiLogger({ cf: request.cf, ctx });
// ...
}
}You can configure retries and a callback for when sending fails.
const logger = getLokiLogger({
retries: 3,
onSendError: (err, msg) => {
console.error("Failed to send to Loki after retries", err);
}
});const logger = getLokiLogger({
silent: process.env.NODE_ENV === 'development',
format: (level, msg, labels) => {
// Return custom LokiMessage structure
return {
streams: [{
stream: { ...labels, level, custom: 'label' },
values: [[Date.now().toString() + "000000", JSON.stringify(msg)]]
}]
};
}
});You can automatically extract labels like http_method, http_url, trace_id (from CF-Ray), and request_id (from X-Request-ID) by passing the Request object.
const logger = getLokiLogger({
request: request // Default request for all logs
});
// Or pass it in a specific log
await logger.info({ message: "API called", request: request });You can measure execution time and log it automatically using the wrap method.
const wrappedFetch = logger.wrap("external-api", async (url) => {
return await fetch(url);
});
const response = await wrappedFetch("https://example.com");
// This will automatically log: Function external-api executed with duration_msWhen NODE_ENV or WORKER_ENV is set to development (or dev), or silent: true is set, cloki will output colorized and formatted logs to the console for better readability.
You can define the allowed label keys using Generics.
type MyLabels = 'env' | 'service' | 'version';
const logger = getLokiLogger<MyLabels>({
defaultLabels: { env: 'prod' } // Type checked
});
await logger.info("Hello", { service: 'api' }); // Type checked
// await logger.info("Hello", { unknown: 'label' }); // TypeScript ErrorContributions are what make the open-source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.
- Fork the Project
- Create your Feature Branch (git checkout -b feature/AmazingFeature)
- Commit your Changes (git commit -m 'Add some AmazingFeature')
- Push to the Branch (git push origin feature/AmazingFeature)
- Open a Pull Request
Distributed under the MIT License. See LICENSE for more information.
miketako3 (Kaito Hiruta) - [email protected]
Project Link: https://github.com/miketako3/cloki