# Tailscale Logs Service

The Tailscale Logs Service defines a REST interface for configuring, storing,
retrieving, and processing log entries.

# Overview

HTTP requests are received at the service **base URL**
[https://log.tailscale.io](https://log.tailscale.io), and return JSON-encoded
responses using standard HTTP response codes.

Authorization for the configuration and retrieval APIs is done with a secret
API key passed as the HTTP basic auth username. Secret keys are generated via
the web UI at base URL. An example of using basic auth with curl:

    curl -u <log_api_key>: https://log.tailscale.io/collections

In the future, an HTTP header will allow using MessagePack instead of JSON.

## Collections

Logs are organized into collections. Inside each collection is any number of
instances.

A collection is a domain name. It is a grouping of related logs. As a
guideline, create one collection per product using subdomains of your
company's domain name. Collections must be registered with the logs service
before any attempt is made to store logs.

## Instances

Each collection is a set of instances. There is one instance per machine
writing logs.

An instance has a name and a number. An instance has a **private** and
**public** ID. The private ID is a 32-byte random number encoded as hex.
The public ID is the SHA-256 hash of the private ID, encoded as hex.

The private ID is used to write logs. The only copy of the private ID
should be on the machine sending logs. Ideally it is generated on the
machine. Logs can be written as soon as a private ID is generated. 

The public ID is used to read and adopt logs. It is designed to be sent
to a service that also holds a logs service API key.

The tailscale logs service will store any logs for a short period of time.
To enable logs retention, the log can be **adopted** using the public ID
and a logs service API key.
Once this is done, logs will be retained long-term (for the configured
retention period).

Unadopted instance logs are stored temporarily to help with debugging:
a misconfigured machine writing logs with a bad ID can be spotted by
reading the logs.
If a public ID is not adopted, storage is tightly capped and logs are
deleted after 12 hours.

# APIs

## Storage

### `POST /c/<collection-name>/<private-ID>` — send a log

The body of the request is JSON.

A **single message** is an object with properties:

`{ }`

The client may send any properties it wants in the JSON message, except
for the `logtail` property which has special meaning. Inside the logtail
object the client may only set the following properties:

- `client_time` in the format of RFC3339: "2006-01-02T15:04:05.999999999Z07:00"

A future version of the logs service API will also support:

- `client_time_offset` a integer of nanoseconds since the client was reset
- `client_time_reset` a boolean if set to true resets the time offset counter

On receipt by the server the `client_time_offset` is transformed into a
`client_time` based on the `server_time` when the first (or
client_time_reset) event was received. 

If any other properties are set in the logtail object they are moved into
the "error" field, the message is saved and a 4xx status code is returned.

A **batch of messages** is a JSON array filled with single message objects:

`[ { }, { }, ... ]`

If any of the array entries are not objects, the content is converted
into a message with a `"logtail": { "error": ...}` property, saved, and
a 4xx status code is returned.

Similarly any other request content not matching one of these formats is
saved in a logtail error field, and a 4xx status code is returned.

An invalid collection name returns `{"error": "invalid collection name"}`
along with a 403 status code.

Clients are encouraged to:

- POST as rapidly as possible (if not battery constrained). This minimizes
  both the time necessary to see logs in a log viewer and the chance of
  losing logs.
- Use HTTP/2 when streaming logs, as it does a much better job of
  maintaining a TLS connection to minimize overhead for subsequent posts.

A future version of logs service API will support sending requests with
`Content-Encoding: zstd`.

## Retrieval

### `GET /collections` — query the set of collections and instances

Returns a JSON object listing all of the named collections.

The caller can query-encode the following fields:

- `collection-name` — limit the results to one collection

    ```
    {
      "collections": {
        "collection1.yourcompany.com": {
          "instances": {
            "<logtail.PublicID>" :{
              "first-seen": "timestamp",
              "size": 4096
            },
            "<logtail.PublicID>" :{
              "first-seen": "timestamp",
              "size": 512000,
              "orphan": true,
            }
          }
        }
      }
    }
    ```

### `GET /c/<collection_name>` — query stored logs

The caller can query-encode the following fields:

- `instances` — zero or more log collection instances to limit results to
- `time-start` — the earliest log to include
- One of:
    - `time-end` — the latest log to include
    - `max-count` — maximum number of logs to return, allows paging
    - `stream` — boolean that keeps the response dangling, streaming in
      logs like `tail -f`. Incompatible with logtail-time-end.

In **stream=false** mode, the response is a single JSON object:

    {
    	// TODO: header fields
    	"logs": [ {}, {}, ... ]
    }

In **stream=true** mode, the response begins with a JSON header object
similar to the storage format, and then is a sequence of JSON log
objects, `{...}`, one per line. The server continues to send these until
the client closes the connection.

## Configuration

For organizations with a small number of instances writing logs, the
Configuration API are best used by a trusted human operator, usually
through a GUI. Organizations with many instances will need to automate
the creation of tokens.

### `POST /collections` — create or delete a collection

The caller must set the `collection` property and `action=create` or
`action=delete`, either form encoded or JSON encoded. Its character set
is restricted to the mundane: [a-zA-Z0-9-_.]+

Collection names are a global space. Typically they are a domain name.

### `POST /instances` — adopt an instance into a collection

The caller must send the following properties, form encoded or JSON encoded:

- `collection` — a valid FQDN ([a-zA-Z0-9-_.]+)
- `instances` an instance public ID encoded as hex

The collection name must be claimed by a group the caller belongs to.
The pair (collection-name, instance-public-ID) may or may not already have
logs associated with it.

On failure, an error message is returned with a 4xx or 5xx status code:

`{"error": "what went wrong"}`