
# tecs.log


Named, leveled logging.

Create one named logger per subsystem. Logger names form the stable filtering
surface:

```teal
local logger <const> = tecs.log.get("game.combat")

logger:info("wave %d spawned %d enemies", 3, 12)
logger:setLevel(tecs.log.DEBUG)
```

Messages go to the platform log destination, including logcat on Android and
Console on Apple platforms.

[`Logger`](/modules/log/#tecs.log.Logger) methods check priority before formatting. Pass raw arguments rather
than formatting first. Guard work that exists only to build a message:

```teal
local snapshot <const> = {frame = 12, player = "Alice"}
if logger:enabled(tecs.log.DEBUG) then
    logger:debug("snapshot: %s", tecs.data.encodeJSON(snapshot))
end
```

## JSON Lines output

Add a queryable file beside the platform destination:

```teal
local path <const> = tecs.io.files.writablePath("game.jsonl")
if not tecs.log.openFile(path) then
    logger:error("could not open %s", path)
end
```

With file output open, the earlier `info` call writes a line like:

```json
{"time":12.345,"level":"INFO","logger":"game.combat","message":"wave 3 spawned 12 enemies"}
```

Each line records elapsed platform time, level, logger, and message.
`closeFile` stops file output and leaves the platform destination active.

## Module contents

### Types

| Type | Kind | Description |
| --- | --- | --- |
| [`Logger`](/modules/log/#tecs.log.Logger) | <span class="tealdoc-kind-badge tealdoc-kind-record">record</span> | Represents a named log category. |

### Functions

| Function | Kind | Description |
| --- | --- | --- |
| [`categoryName`](/modules/log/#tecs.log.categoryName) | <span class="tealdoc-kind-badge tealdoc-kind-static">Static</span> | Returns the name registered for a category. |
| [`closeFile`](/modules/log/#tecs.log.closeFile) | <span class="tealdoc-kind-badge tealdoc-kind-static">Static</span> | Stops writing to the file. |
| [`filePath`](/modules/log/#tecs.log.filePath) | <span class="tealdoc-kind-badge tealdoc-kind-static">Static</span> | Returns the open log file path, or nil when no file is open. |
| [`get`](/modules/log/#tecs.log.get) | <span class="tealdoc-kind-badge tealdoc-kind-static">Static</span> | Returns the logger for name, creating it the first time. |
| [`loggers`](/modules/log/#tecs.log.loggers) | <span class="tealdoc-kind-badge tealdoc-kind-static">Static</span> | Returns every registered logger name. |
| [`openFile`](/modules/log/#tecs.log.openFile) | <span class="tealdoc-kind-badge tealdoc-kind-static">Static</span> | Writes every accepted line to path as JSON Lines and truncates the file. |
| [`setLevel`](/modules/log/#tecs.log.setLevel) | <span class="tealdoc-kind-badge tealdoc-kind-static">Static</span> | Sets the minimum priority for every category. |

### Values

| Value | Type | Description |
| --- | --- | --- |
| [`CRITICAL`](/modules/log/#tecs.log.CRITICAL) | `integer` | Read-only. Contains the critical priority. |
| [`DEBUG`](/modules/log/#tecs.log.DEBUG) | `integer` | Read-only. Contains the debug priority. |
| [`ERROR`](/modules/log/#tecs.log.ERROR) | `integer` | Read-only. Contains the error priority. |
| [`INFO`](/modules/log/#tecs.log.INFO) | `integer` | Read-only. Contains the informational priority. |
| [`TRACE`](/modules/log/#tecs.log.TRACE) | `integer` | Read-only. Contains the trace priority. |
| [`VERBOSE`](/modules/log/#tecs.log.VERBOSE) | `integer` | Read-only. Contains the verbose priority. |
| [`WARN`](/modules/log/#tecs.log.WARN) | `integer` | Read-only. Contains the warning priority. |

## Types

<a id="tecs.log.Logger"></a>
### tecs.log.Logger <span class="tealdoc-kind-badge tealdoc-kind-record">record</span>

Represents a named log category.


```teal
record tecs.log.Logger
    name: string
    category: integer

    critical: function(self, message: string, ...: any)
    debug: function(self, message: string, ...: any)
    enabled: function(self, priority: integer): boolean
    error: function(self, message: string, ...: any)
    info: function(self, message: string, ...: any)
    level: function(self): integer
    setLevel: function(self, priority: integer)
    trace: function(self, message: string, ...: any)
    verbose: function(self, message: string, ...: any)
    warn: function(self, message: string, ...: any)
end
```

<a id="tecs.log.Logger.name"></a>
#### tecs.log.Logger.name <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

Read-only. Contains the registered logger name written to the
platform log.


```teal
tecs.log.Logger.name: string
```

<a id="tecs.log.Logger.category"></a>
#### tecs.log.Logger.category <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

Engine-owned. Contains the category allocated during this run.
The number may change across runs, so game code should filter by
`name`.


```teal
tecs.log.Logger.category: integer
```

<a id="tecs.log.Logger.critical"></a>
#### tecs.log.Logger:critical <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

Logs at `CRITICAL`. See `trace` for the formatting rules.


```teal
function tecs.log.Logger.critical(self, message: string, ...: any)
```

##### Arguments

| Name | Type | Description |
| --- | --- | --- |
| `self` | `Logger` |  |
| `message` | `string` |  |
| `...` | `any` |  |

##### Returns

None.

<a id="tecs.log.Logger.debug"></a>
#### tecs.log.Logger:debug <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

Logs at `DEBUG`. See `trace` for the formatting rules.


```teal
function tecs.log.Logger.debug(self, message: string, ...: any)
```

##### Arguments

| Name | Type | Description |
| --- | --- | --- |
| `self` | `Logger` |  |
| `message` | `string` |  |
| `...` | `any` |  |

##### Returns

None.

<a id="tecs.log.Logger.enabled"></a>
#### tecs.log.Logger:enabled <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

Reports whether the logger would emit a message at `priority`.

Call this directly to guard work that only exists to build a log message,
such as serializing a table. The level methods already guard themselves.


```teal
function tecs.log.Logger.enabled(self, priority: integer): boolean
```

##### Arguments

| Name | Type | Description |
| --- | --- | --- |
| `self` | `Logger` |  |
| `priority` | `integer` |  |

##### Returns

| Type | Description |
| --- | --- |
| `boolean` |  |

<a id="tecs.log.Logger.error"></a>
#### tecs.log.Logger:error <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

Logs at `ERROR`. See `trace` for the formatting rules. Logging is all this does; it neither
raises nor returns anything.


```teal
function tecs.log.Logger.error(self, message: string, ...: any)
```

##### Arguments

| Name | Type | Description |
| --- | --- | --- |
| `self` | `Logger` |  |
| `message` | `string` |  |
| `...` | `any` |  |

##### Returns

None.

<a id="tecs.log.Logger.info"></a>
#### tecs.log.Logger:info <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

Logs at `INFO`. See `trace` for the formatting rules.


```teal
function tecs.log.Logger.info(self, message: string, ...: any)
```

##### Arguments

| Name | Type | Description |
| --- | --- | --- |
| `self` | `Logger` |  |
| `message` | `string` |  |
| `...` | `any` |  |

##### Returns

None.

<a id="tecs.log.Logger.level"></a>
#### tecs.log.Logger:level <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

Returns the minimum priority this logger emits.



```teal
function tecs.log.Logger.level(self): integer
```

##### Arguments

| Name | Type | Description |
| --- | --- | --- |
| `self` | `Logger` |  |

##### Returns

| Type | Description |
| --- | --- |
| `integer` | Returns the platform priority until something sets one. |

<a id="tecs.log.Logger.setLevel"></a>
#### tecs.log.Logger:setLevel <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

Sets the minimum priority this logger emits.



```teal
function tecs.log.Logger.setLevel(self, priority: integer)
```

##### Arguments

| Name | Type | Description |
| --- | --- | --- |
| `self` | `Logger` |  |
| `priority` | `integer` | One of the constants on `log`. The logger emits messages at this priority and drops anything lower. |

##### Returns

None.

<a id="tecs.log.Logger.trace"></a>
#### tecs.log.Logger:trace <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

Logs at `TRACE` and applies `string.format` specifiers only when emitted.

The method checks the level before formatting, so a filtered call costs a load and a
compare and the arguments are never touched. Building those arguments is still the
caller's cost; guard that with `enabled`.



```teal
function tecs.log.Logger.trace(self, message: string, ...: any)
```

##### Arguments

| Name | Type | Description |
| --- | --- | --- |
| `self` | `Logger` |  |
| `message` | `string` | A `string.format` template when arguments follow, and a literal otherwise, so a `%` in a plain message is safe. |
| `...` | `any` | Formatted into `message`. A wrong count or a wrong specifier raises from `string.format`, and only on the calls that are actually emitted. |

##### Returns

None.

<a id="tecs.log.Logger.verbose"></a>
#### tecs.log.Logger:verbose <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

Logs at `VERBOSE`. See `trace` for the formatting rules.


```teal
function tecs.log.Logger.verbose(self, message: string, ...: any)
```

##### Arguments

| Name | Type | Description |
| --- | --- | --- |
| `self` | `Logger` |  |
| `message` | `string` |  |
| `...` | `any` |  |

##### Returns

None.

<a id="tecs.log.Logger.warn"></a>
#### tecs.log.Logger:warn <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

Logs at `WARN`. See `trace` for the formatting rules.


```teal
function tecs.log.Logger.warn(self, message: string, ...: any)
```

##### Arguments

| Name | Type | Description |
| --- | --- | --- |
| `self` | `Logger` |  |
| `message` | `string` |  |
| `...` | `any` |  |

##### Returns

None.

## Functions

<a id="tecs.log.categoryName"></a>
### tecs.log.categoryName <span class="tealdoc-kind-badge tealdoc-kind-static">Static</span>

Returns the name registered for a category.



```teal
function tecs.log.categoryName(category: integer): string
```

#### Arguments

| Name | Type | Description |
| --- | --- | --- |
| `category` | `integer` |  |

#### Returns

| Type | Description |
| --- | --- |
| `string` | Never returns nil. An unregistered category comes back as `"category:<number>"` rather than as an error. |

<a id="tecs.log.closeFile"></a>
### tecs.log.closeFile <span class="tealdoc-kind-badge tealdoc-kind-static">Static</span>

Stops writing to the file.

Does nothing when no file is open, so it is safe to call on a shutdown path that does
not know whether one was ever asked for.


```teal
function tecs.log.closeFile()
```

#### Arguments

None.

#### Returns

None.

<a id="tecs.log.filePath"></a>
### tecs.log.filePath <span class="tealdoc-kind-badge tealdoc-kind-static">Static</span>

Returns the open log file path, or nil when no file is open.



```teal
function tecs.log.filePath(): string
```

#### Arguments

None.

#### Returns

| Type | Description |
| --- | --- |
| `string` | The path as it was given, not resolved against the working directory, so a relative one is only meaningful to a reader in the same place the game ran. |

<a id="tecs.log.get"></a>
### tecs.log.get <span class="tealdoc-kind-badge tealdoc-kind-static">Static</span>

Returns the logger for `name`, creating it the first time.

Names are the unit of filtering, so they are what a subsystem should use:
`tecs.gfx`, `tecs.debug.events`. Each maps to one SDL category, which
is what `SDL_SetLogPriority` takes.



```teal
function tecs.log.get(name: string): log.Logger
```

#### Arguments

| Name | Type | Description |
| --- | --- | --- |
| `name` | `string` | Compared byte for byte, so two spellings are two categories with two levels. |

#### Returns

| Type | Description |
| --- | --- |
| [`log.Logger`](/modules/log/#tecs.log.Logger) | Returns the same object on every call for a name. A new logger starts at the platform's default priority. |

<a id="tecs.log.loggers"></a>
### tecs.log.loggers <span class="tealdoc-kind-badge tealdoc-kind-static">Static</span>

Returns every registered logger name.



```teal
function tecs.log.loggers(): {string}
```

#### Arguments

None.

#### Returns

| Type | Description |
| --- | --- |
| `{string}` | A fresh array each call, sorted, holding only names something has already asked `get` for. A subsystem that has not run yet is absent. |

<a id="tecs.log.openFile"></a>
### tecs.log.openFile <span class="tealdoc-kind-badge tealdoc-kind-static">Static</span>

Writes every accepted line to `path` as JSON Lines and truncates the file.

The platform destination keeps receiving the human-readable form, so
The platform still sends its human-readable stream to `tail -f`, logcat,
or Console.app. The file makes a
log queryable after the fact: seek to an offset, read to the end.

Calling this while another file is open closes the previous file and moves
subsequent lines to `path`.



```teal
function tecs.log.openFile(path: string): boolean
```

#### Arguments

| Name | Type | Description |
| --- | --- | --- |
| `path` | `string` | Truncated if it exists, created if it does not, including when it is the file already open, so this is not a way to check one is. Directories are not made. |

#### Returns

| Type | Description |
| --- | --- |
| `boolean` | False when the file cannot open. In that case nothing else changes and a file already open keeps receiving lines. |

<a id="tecs.log.setLevel"></a>
### tecs.log.setLevel <span class="tealdoc-kind-badge tealdoc-kind-static">Static</span>

Sets the minimum priority for every category.



```teal
function tecs.log.setLevel(priority: integer)
```

#### Arguments

| Name | Type | Description |
| --- | --- | --- |
| `priority` | `integer` | Overrides every per-logger level already set, so this is a reset and not a floor. |

#### Returns

None.

## Values

<a id="tecs.log.CRITICAL"></a>
### tecs.log.CRITICAL <span class="tealdoc-kind-badge tealdoc-kind-variable">variable</span>

Read-only. Contains the critical priority.


```teal
tecs.log.CRITICAL: integer
```

<a id="tecs.log.DEBUG"></a>
### tecs.log.DEBUG <span class="tealdoc-kind-badge tealdoc-kind-variable">variable</span>

Read-only. Contains the debug priority.


```teal
tecs.log.DEBUG: integer
```

<a id="tecs.log.ERROR"></a>
### tecs.log.ERROR <span class="tealdoc-kind-badge tealdoc-kind-variable">variable</span>

Read-only. Contains the error priority.


```teal
tecs.log.ERROR: integer
```

<a id="tecs.log.INFO"></a>
### tecs.log.INFO <span class="tealdoc-kind-badge tealdoc-kind-variable">variable</span>

Read-only. Contains the informational priority.


```teal
tecs.log.INFO: integer
```

<a id="tecs.log.TRACE"></a>
### tecs.log.TRACE <span class="tealdoc-kind-badge tealdoc-kind-variable">variable</span>

Read-only. Contains the trace priority.


```teal
tecs.log.TRACE: integer
```

<a id="tecs.log.VERBOSE"></a>
### tecs.log.VERBOSE <span class="tealdoc-kind-badge tealdoc-kind-variable">variable</span>

Read-only. Contains the verbose priority.


```teal
tecs.log.VERBOSE: integer
```

<a id="tecs.log.WARN"></a>
### tecs.log.WARN <span class="tealdoc-kind-badge tealdoc-kind-variable">variable</span>

Read-only. Contains the warning priority.


```teal
tecs.log.WARN: integer
```