description |
---|
Send Javascript logs to Timber from your Node or Browser environment |
Timber integrates with Javascript through its universal Javascript library, enabling you to send Javascript logs from your Node Or Browser environment to your Timber account.
- ****Universal Node/browser support.
- ****NPM or CDN.
- ****Written in Typescript; runs anywhere.
- ****Blazing fast.
- ****Guaranteed consistency.
- ****Easy logging middleware.
- Light as a feather. The gzipped browser bundle weighs in at just 4.3kb!
- ****Plays nicely with other loggers.
To quickly demonstrate the power of Timber, the following example logs a simple structured log with context:
import { ITimberLog } from "@timberio/types";
// Add middleware for context
async function addCurrentUser(log: ITimberLog): Promise<ITimberLog> {
return {
...log,
user: {id: 1000, name: "Lee"},
};
}
timber.use(addCurrentUser);
timber.info(
"Order #1234 placed, total: $500.23",
{order_placed: {id: 1234, total: 500.23}}
).then(log => {
// At this point, your log is synced with Timber.io!
});
This produces the following JSON log line:
{
"dt": "2019-03-04T03:44:21.221232Z",
"level": "info",
"message": "Order #1234 placed, total: $500.23",
"order_placed": {
"id": 1234,
"total": 500.23
},
"context": {
"system": {
"hostname": "ec2-44-125-241-8"
},
"user": {
"id": "1000",
"name": "Lee"
}
}
}
Continue to learn more about setting context, automatic context, structured logging, and more.
{% hint style="info" %} We recommend the "HTTP" method for your environment if you are unsure. To understand why you would choose one over the other, please see the "Ship Logs From Within My App?" guide. {% endhint %}
{% tabs %} {% tab title="Node (HTTP)" %} Send logs directly from within your app over HTTP:
-
Install the Timber Node library:
npm i @timberio/node
-
Import the Node logger:
In ES6/Typescript, import the
Timber
class:import { Timber } from "@timberio/node";
Or, for CommonJS, require the package:
const { Timber } = require("@timberio/node");
-
Create a new logger, replace
YOUR_API_KEY
andYOUR_SOURCE_ID
accordingly:const logger = new Timber("YOUR_API_KEY", "YOUR_SOURCE_ID");
Note: Timber integrates with popular 3rd party logging libraries if you'd prefer to use those. {% endtab %}
{% tab title="Node (STDOUT)" %}
Write logs to STDOUT
and ship them external from your app:
{% hint style="warning" %}
This method is more advanced and requires a separate step to ship logs to Timber. Basic knowledge of STDOUT
and log management is required. For more information on the advantages of this method please see this guide.
{% endhint %}
-
Install the Timber Node library:
npm i @timberio/node
-
Import the Node logger:
In ES6/Typescript, import the
Timber
class:import { Timber } from "@timberio/node";
Or, for CommonJS, require the package:
const { Timber } = require("@timberio/node");
-
Create a new logger, replace
YOUR_API_KEY
andYOUR_SOURCE_ID
accordingly:const logger = new Timber("YOUR_API_KEY", "YOUR_SOURCE_ID"); timber.setSync(async logs => { logs.forEach(log => console.log(log)); return logs; })
Note: Timber integrates with popular 3rd party logging libraries if you'd prefer to use those.
-
At this point your application is writing logs to
STDOUT
in JSON format. Please choose the appropriate platform, log forwarder, or operating system. {% endtab %}
{% tab title="Browser (NPM)" %} For browser based environments. If you're using a module bundler like Webpack or Rollup, you can install the package directly from NPM:
-
Install the Timber browser library:
npm i @timberio/browser
-
Import the
Timber
class:In ES6/Typescript, import the
Timber
class:import { Timber } from "@timberio/browser";
Or, for CommonJS, require the package:
const { Timber } = require("@timberio/browser");
-
Create a new logger with your source ID and API key:
const logger = new Timber("YOUR_API_KEY", "YOUR_SOURCE_ID");
Note: Timber integrates with popular 3rd party logging libraries if you'd prefer to use those. {% endtab %}
{% tab title="Browser (CDN)" %}
For Browser based environments. If you're not using a Node.js module bundler, you can log in any client-side app by dropping in a <script>
tag:
-
Install the Timber browser library:
<script src="https://unpkg.com/@timberio/browser@0.X/dist/umd/timber.js"></script>
This will place the
Timber
class onwindow.Timber
-
Create a new logger with your source ID and API key:
const logger = new window.Timber("YOUR_API_KEY", "YOUR_SOURCE_ID");
Note: Timber integrates with popular 3rd party logging libraries if you'd prefer to use those. {% endtab %}
{% tab title="Other" %} Timber integrates with other popular loggers, such as Bunyan, Pino, and Winston. See the Integrations section for a complete list. {% endtab %} {% endtabs %}
The Timber
constructor takes a list of options as defined by ITimberOptions
type in the @timberio/types
package. Please see the @timber/type
docs for a full list of options.
Here we pass the ignoreExceptions
option which is a boolean to specify whether thrown errors/failed logs should be ignored:
const logger = new Timber("YOUR_API_KEY", "YOUR_SOURCE_ID", {ignoreExceptions: true});
{% hint style="info" %} Timber integrates with popular Node logging libraries, allowing you to use their interfaces for logging. Please see the integrations section for more information. {% endhint %}
Basic leveled logging statements can be made using the appropriate method (debug
, info
, warn
, or error
):
// Use any level debug, info, warn, or error
logger.info("Hello world")
All log levels return a Promise that will resolve once the log has been synced with Timber:
// Will resolve when synced with Timber.io (or reject if there's an error)
timber.log("some log message").then(log => {
// `log` is the transformed log, after going through middleware
});
{% hint style="info" %} If you haven't already, please read our structured logging best practices guide. {% endhint %}
Augment your logs with structured data:
timber.info("Order #1234 placed, total: $500.23", {
order_placed: {
id: 1234,
total: 500.23
},
}).then(log => {
// At this point, your log is synced with Timber.io!
});
For completeness with our other libraries, we added this section. Adding context is achieved by adding middleware. Please see:
You can add your own middleware functions, which act as transforms on the ITimberLog
log object.
This is useful for augmenting the log prior to syncing with Timber, or even pushing the log to another service.
Here's what a middleware function looks like:
import { ITimberLog } from "@timberio/types";
// In this example function, we'll add custom 'context' meta to the log
// representing the currently logged in user.
//
// Note: a middleware function is any function that takes an `ITimberLog`
// and returns a `Promise<ITimberLog>`
async function addCurrentUser(log: ITimberLog): Promise<ITimberLog> {
return {
...log, // <-- copy the existing log
user: {
// ... and add our own `context` data
id: 1000,
name: "Lee",
},
};
}
Then just attach to the Timber instance with .use
:
timber.use(addCurrentUser);
You can add any number of pipeline functions to your logger
instance, and they'll run in order.
Middleware functions run before the final sync to Timber.io. Pipeline functions should return a Promise<ITimberLog>
, making it possible to augment logs with asynchronous data from external sources.
{% hint style="danger" %}
If an exception is thrown anywhere in the pipeline chain the log won't be synced! Wrap an async try/catch
block around your call to .log|info|debug|warn|error()
or tack on a catch()
to ensure your errors are handled.
{% endhint %}
If you wish to remove middleware, pass in the original middleware function to .remove()
:
// `addCurrentUser` will no longer be used to transform logs
timber.remove(addCurrentUser);
This will remove the middleware function from all future calls to .log|info|debug|warn|error()
.
To re-add middleware, pass it to .use()
You can associate your logs to users by adding user context. This is achieved by adding middleware immediately after logging the user in:
import { ITimberLog } from "@timberio/types";
// In this example function, we'll add custom 'context' meta to the log
// representing the currently logged in user.
//
// Note: a middleware function is any function that takes an `ITimberLog`
// and returns a `Promise<ITimberLog>`
async function addCurrentUser(log: ITimberLog): Promise<ITimberLog> {
return {
...log, // <-- copy the existing log
user: {
// ... and add our own `context` data
id: 1000,
name: "Lee",
},
};
}
timber.use(addCurrentUser);
You can associate your logs to HTTP requests by adding HTTP context. This is achieved by adding a Timber middleware within your HTTP processing pipeline:
Another common practice for Timber users is to tie their logs to HTTP requests through the HTTP request ID. That's makes it easy to logs relating to an entire HTTP transaction. We recommend adding middleware at the top of your callback chain, and then removing the middleware immediately after.
Timber has the ability to pipe logs to any writable Stream
. This includes STDOUT
, STDERR
, or even a network Socket.
{% hint style="info" %}
If you have the means to log to STDOUT,
we highly recommend that you redirect STDOUT to Timber through one of our platform, log forwarder, or operating system integrations instead of shipping logs from within your app. You can read more about that here.
{% endhint %}
Logging to STDOUT
is as simple as piping output to the process.stdout
stream:
const timber = new Timber("YOUR_API_KEY", "YOUR_SOURCE_ID");
timber.pipe(process.stdout); // <-- this will send a copy to `STDOUT`
timber.log("This will also show up in the console...");
Timber is a universal logging library and ships with the following pages.
Logging works in Node.js or the browser. Choose the appropriate lib for installation instructions. Please see the "Which package should I install?" FAQ for more information.
Package | What's it for? |
---|---|
@timberio/node |
Node.js logger |
@timberio/browser |
Browser logger |
@timberio/js |
Node.js/browser logging, in a single package |
There are a few helper libraries available that you typically won't need to use directly:
Package | What's it for? |
---|---|
@timberio/core |
Core library to extend for custom loggers |
@timberio/tools |
Tools/utils used by loggers for throttling, batching, queuing logs |
@timberio/types |
Shared Typescript types |
Timber integrates with popular 3rd party libraries such as Winston
, Bunyan
, Express
, and Koa
. Please see the Javascript integrations section:
{% page-ref page="integrations/" %}
The Timber library provides out-the-box defaults for batching calls to .log()
and throttling synchronization with Timber.io, aiming to provide a balance between strong performance and sensible resource usage.
We believe a logging library should be a good citizen of your stack - avoiding unnecessary slow-downs in your app due to excessive network I/O or large memory usage.
By default, the library will batch up to 1,000 logs at a time (syncing after 1,000ms, whichever is sooner), and open up to 5concurrent network requests to Timber.io for syncing.
These defaults can be tweaked by passing custom options when creating your Timber
instance:
const timber = new Timber("timber-organization-key", "timber-source-key", {
// Maximum number of logs to sync in a single request to Timber.io
batchSize: 1000,
// Max interval (in milliseconds) before a batch of logs proceeds to syncing
batchInterval: 1000,
// Maximum number of sync requests to make concurrently (useful to limit
// network I/O)
syncMax: 100, // <-- we've increased concurrent network connections up to 100
// Boolean to specify whether thrown errors/failed logs should be ignored
ignoreExceptions: false,
});
This table shows the time to synchronize 10,000 logs raised by calling .log()
10,000x on a 2.2Ghz Intel Core i7 Macbook Pro with 16GB of RAM based in the UK, calling the Timber.io service hosted in US-East, based on various syncMax
connections:
Connections | Time to sync 10k logs (in ms) | Improvement vs. last |
---|---|---|
1 | 72506.09 | - |
2 | 34852.11 | 108.04% |
5 | 14488.63 | 140.55% |
10 | 7501.31 | 93.15% |
20 | 3876.49 | 93.51% |
50 | 2150.37 | 80.27% |
100 | 1736.81 | 23.81% |
200 | 1706.66 | 1.77% |
In general, a higher syncMax
number (i.e. number of concurrent throttled connections made to Timber.io) will provide linear improvements in total time to sync logs vs. lower numbers.
Bear in mind:
- This simple benchmark was performed on 10,000 immediate calls to
.log()
, which is unlikely to represent typical usage. A more common pattern in a typical app would be periodic log events, followed by periods of inactivity. - Although higher numbers naturally synchronize more quickly (due to concurrent network calls to timber.io), most apps will run sufficiently with just 1-2 open network requests since it's likely that the default
batchSize: 1000
will be enough to capture 99.9% of logging workloads within the defaultbatchInterval: 1000
ms time period before proceeding to sync with Timber servers. - Performance drops sharply after 50 concurrent connections (and infers almost no extra benefit after 100), likely due to the latency between the test machine and the Timber.io server.
- A typical app won't need to consider performance; the defaults will be be sufficient.
Based on your logging use-case, the following base-line recommendations can be considered when instantiating a new Timber
instance:
If you have... | Recommendations |
---|---|
A large number of logs, fired frequently, and want them to sync very quickly | Increase syncMax (50 -100 is a good default); lower batchInterval to 200 ms to emit faster |
Logs events that occur less frequently | Decrease batchInterval to 100 ms, so synchronization with Timber will start within 1/10th of a second |
An app that needs to preserve network I/O or limit outgoing requests | Drop syncMax to 5 ; increase batchInterval to 2000 ms to fire less often |
Intermittent periods of large logging activity (and you want fast syncing), followed by inactivity | Increase syncMax to 20 to 'burst' connections as needed; lower batchSize to match your typical log activity to emit faster |
Low log activity, that you want to sync with Timber ASAP | Lower batchInterval to 10 ms, so synchronization with Timber occurs very quickly |
Or, you can simply leave the default settings in place, which will be adequate for the vast majority of applications.
When you log via .log()
, you're creating a Promise that resolves when the log has been synchronized with Timber.io
This provides a mechanism for guaranteed consistency in your application.
While there are no hard limits on firing .log()
events, the effective limit of concurrent logs will be determined by memory available to your Node.js V8 process (or in the user's browser, for browser logging.) to create a new stack for each Promise executed.
As a general rule, you might run into stack size limits beyond 100,000 - 500,000 concurrent logs -- therefore, we recommend adjusting your syncMax
, batchSize
and batchInterval
settings to ensure that logs aren't sitting around in memory for long periods of time, awaiting synchronization with Timber.io.
This is a sensible strategy regardless of memory limits, to ensure logs are being written consistently to Timber and are not at risk of loss due to an app crash or server downtime.
Note: This only applies if you are working with an application that emits a large number of logs (100,000+) at short intervals (within a 5-10 second window.) 99%+ of apps will see adequate performance with the default settings.
For most Javascript projects, just npm i @timberio/js
.
This will install both the Node.js and browser versions in a single package.
Alternatively, if you only need to log in a single environment, import @timberio/browser
or @timberio/node
directly.
Both loggers extend @timberio/core
, and share the same .log()
API.
But each offer unique features optimized for the target environment.
For example, the browser version uses built-in features of a client's web browser and client-side fetch()
to synchronize logs with Timber.io. The result is a fast, and nimble <script>
tag or bundle that drops easily into any client-side app.
The Node version includes features such as msgpack encoding, logging from streams and deep exception handling (coming soon!), which are specific to Node.js and would have no purpose in a client-side logger.
Importing a distinct version of the logger makes it simple for bundlers like Parcel or Webpack to leave out the parts that aren't required in the browser, resulting in a much smaller download — only 4.3kb!
Yes! Every part of the Timber JS library is written in Typescript, so you get full types (and documentation) right out-the-box.
Nope. This library and its associated components are laid out as a mono-repo using Lerna, to make it easier to maintain all code from one home on Github.
@timberio/logger
is the private name that governs all code, tests and other stuff that only concerns the maintainers of this library, and is not importable.
Instead, simply use @timberio/js
To begin, please see our log delivery troubleshooting guide. This covers the most common issues we see with log delivery:
{% page-ref page="../../../guides/troubleshooting-log-delivery.md" %}
If the above troubleshooting guide does not resolve your issue please contact support.