Skip to content

Commit 7ef96b7

Browse files
committed
feat: enhance documentation and add new features for Seq logger integration
1 parent b9519f8 commit 7ef96b7

11 files changed

Lines changed: 552 additions & 76 deletions

‎README.md‎

Lines changed: 53 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,77 @@
1-
## Usage of my_logger
1+
# my-logger
22

3+
A logger for Rust applications. Every record is printed to the console and handed to the plugged consumers. The Seq logger is such a consumer: it sends the records to [Seq](https://datalust.co/seq) or to my-logger-server.
34

4-
### How to use it in your application
5-
6-
7-
Plug it
5+
## Plug it
86

97
```toml
108
[dependencies]
11-
my-logger = { tag = "max_version", git = "https://github.com/MyJetTools/my-logger.git" }
9+
my-logger = { tag = "x.x.x", git = "https://github.com/MyJetTools/my-logger.git" }
1210
```
1311

12+
`tag` is the latest of [the tags](https://github.com/MyJetTools/my-logger/tags).
1413

15-
Put one of these lines at any place in your code
16-
```rust
17-
18-
// writing information line
19-
my_logger::LOGGER.write_info("process_name",
20-
"message",
21-
LogEventCtx::new()
22-
.add("field1","value1")
23-
.add("field2","value2")
24-
.add("field3","value3")
25-
);
26-
27-
// writing warning line
28-
my_logger::LOGGER.write_warning("process_name",
29-
"message",
30-
LogEventCtx::new()
31-
.add("field1","value1")
32-
.add("field2","value2")
33-
.add("field3","value3")
34-
);
14+
Write a record at any place of the code:
3515

36-
// writing error line
37-
my_logger::LOGGER.write_error("process_name",
38-
"message",
39-
LogEventCtx::new()
40-
.add("field1","value1")
41-
.add("field2","value2")
42-
.add("field3","value3")
16+
```rust
17+
use my_logger::LogEventCtx;
18+
19+
my_logger::LOGGER.write_info(
20+
"process_name",
21+
"message",
22+
LogEventCtx::new()
23+
.add("field1", "value1")
24+
.add("field2", "value2"),
4325
);
26+
```
4427

45-
// writing fatal error line
46-
my_logger::LOGGER.write_fatal_error("process_name",
47-
"message",
48-
LogEventCtx::new()
49-
.add("field1","value1")
50-
.add("field2","value2")
51-
.add("field3","value3")
52-
);
28+
## Plug the Seq logger
5329

54-
// writing fatal debug error line
55-
my_logger::LOGGER.write_fatal_debug("process_name",
56-
"message",
57-
LogEventCtx::new()
58-
.add("field1","value1")
59-
.add("field2","value2")
60-
.add("field3","value3")
61-
);
30+
To send the records to Seq or to my-logger-server, turn the `my-seq-logger` feature on:
6231

32+
```toml
33+
[dependencies]
34+
my-logger = { tag = "x.x.x", git = "https://github.com/MyJetTools/my-logger.git", features = [
35+
"my-seq-logger",
36+
] }
37+
async-trait = "*"
38+
tokio = { version = "*", features = ["full"] }
6339
```
6440

65-
### How to configure
66-
Some context values can be pre-populated by adding the line in the **fn main()**
41+
Give it the connection string and enable it once, at the start of the application:
42+
6743
```rust
68-
#[tokio::main]
69-
async fn main() {
70-
my_logger::LOGGER.populate_params("key","value");
71-
my_logger::LOGGER.populate_params("key2","value2");
44+
use std::sync::Arc;
45+
46+
use my_logger::my_seq_logger::{SeqLogger, SeqSettings};
47+
48+
pub struct SettingsReader;
49+
50+
#[async_trait::async_trait]
51+
impl SeqSettings for SettingsReader {
52+
async fn get_conn_string(&self) -> String {
53+
"url=http://127.0.0.1:5341;apikey=my-api-key;product=my-product".to_string()
54+
}
7255
}
73-
```
74-
To configure application name and version method can be used
75-
```rust
56+
7657
#[tokio::main]
7758
async fn main() {
78-
my_logger::LOGGER.populate_app_and_version(env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"));
59+
my_logger::LOGGER.populate_app_and_version(env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"));
60+
61+
SeqLogger::enable_from_connection_string(Arc::new(SettingsReader)).await;
7962
}
8063
```
8164

82-
# my-seq-logger
83-
Seq Logger
84-
85-
Please Use wiki to read documentation
86-
https://github.com/MyJetTools/my-seq-logger/wiki
65+
A service built on service-sdk needs none of this: `ServiceContext::new` makes both calls itself and takes the connection string from `seq_conn_string` of the settings.
8766

67+
## Features
8868

89-
### 0.2.1
90-
* Settings now are read each fail case
69+
| Feature | In short |
70+
|---|---|
71+
| [Levels, context and macros](docs/writing-logs.md) | `write_info`, `write_warning`, `write_error`, `write_fatal_error` and `write_debug`, each with a context of keys and values; the `write_log_*!` macros. |
72+
| [Parameters of every record](docs/populated-params.md) | `Application`, `Version`, `EnvInfo` and the ones of your own, added to each record which is sent. |
73+
| [Console and counters](docs/console-and-counters.md) | Every record is printed to the console and counted by its level. |
74+
| [Consumers](docs/consumers.md) | A destination of your own for the records. |
75+
| [Seq logger](docs/seq-logger.md) | Sends the records to Seq or to my-logger-server in the background and writes a panic as a fatal error. |
76+
| [Connection string](docs/seq-connection-string.md) | `url`, `apikey`, `product`, `timeout` and `queuesize` of the Seq logger, read again before every request. |
77+
| [Product scope](docs/seq-connection-string.md#product-scope) | `product=my-product` names the product scope of my-logger-server the records go to. |

‎docs/console-and-counters.md‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Console and counters
2+
3+
## Console
4+
5+
Every record is printed to the console as soon as it is written, with or without consumers plugged:
6+
7+
```
8+
2026-10-12T09:24:22.984955+00:00 Info
9+
Process: process_name
10+
Message: message
11+
Context: {"field1": "value1", "field2": "value2"}
12+
-------------------
13+
```
14+
15+
- `Info`, `Warning` and `Debug` go to stdout, `Error` and `FatalError` go to stderr.
16+
- The `Context` line is there when the record has a context.
17+
- The [parameters of every record](populated-params.md) are not printed.
18+
19+
## Counters
20+
21+
The records are counted by their level from the start of the application:
22+
23+
```rust
24+
let errors = my_logger::LOGGER.get_errors_amount();
25+
```
26+
27+
| Method | Counts |
28+
|---|---|
29+
| `get_infos_amount()` | `Info` |
30+
| `get_warnings_amount()` | `Warning` |
31+
| `get_errors_amount()` | `Error` |
32+
| `get_fatal_errors_amount()` | `FatalError` |
33+
| `get_debugs_amount()` | `Debug` |

‎docs/consumers.md‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Consumers
2+
3+
A consumer is a destination of the records. Every record written after a consumer is plugged is handed to it; any number of consumers can be plugged. The [Seq logger](seq-logger.md) is a consumer.
4+
5+
```rust
6+
use std::sync::Arc;
7+
8+
use my_logger::{MyLogEvent, MyLoggerConsumer};
9+
10+
pub struct MyConsumer;
11+
12+
impl MyLoggerConsumer for MyConsumer {
13+
fn write_log(&self, log_event: Arc<MyLogEvent>) {
14+
// put the record into a queue of your own
15+
}
16+
}
17+
18+
my_logger::LOGGER.plug_consumer(Arc::new(MyConsumer));
19+
```
20+
21+
`write_log` is called on the thread which writes the record, before the write returns. It must not block and has no Tokio runtime to rely on: put the record into a queue and do the work in the background.
22+
23+
## MyLogEvent
24+
25+
| Field | |
26+
|---|---|
27+
| `dt` | When the record is written. |
28+
| `level` | `Info`, `Warning`, `Error`, `FatalError` or `Debug`. |
29+
| `process` | The process of the record. |
30+
| `message` | The message of the record. |
31+
| `context` | The keys and values of the record: `None` when there are none. |
32+
33+
The [parameters of every record](populated-params.md) are not a part of the event: a consumer takes them with `my_logger::LOGGER.get_populated_params()`.

‎docs/populated-params.md‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Parameters of every record
2+
3+
Parameters populated once are added to every record the [Seq logger](seq-logger.md) sends.
4+
5+
```rust
6+
my_logger::LOGGER.populate_app_and_version(env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"));
7+
my_logger::LOGGER.populate_params("Region", "eu");
8+
```
9+
10+
| Call | Adds |
11+
|---|---|
12+
| `populate_app_and_version(name, version)` | `Application` and `Version`. With the `ENV_INFO` environment variable set - `EnvInfo` with its value as well. |
13+
| `populate_params(key, value)` | A parameter of your own. The key is a `&'static str`, the value is a `&'static str` or a `String`. |
14+
15+
- Populate them at the start of the application: a record sent before that goes without them.
16+
- Every call adds. A key populated twice is sent twice, so each of them is called once.
17+
- They are not printed to the console.
18+
- `my_logger::LOGGER.get_populated_params()` returns what is populated.
19+
20+
`Application` is also what my-logger-server finds the product scope of a record by, when the connection string names no [product](seq-connection-string.md#product-scope).

‎docs/seq-connection-string.md‎

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Connection string
2+
3+
What the [Seq logger](seq-logger.md) is set up with. The application gives it through `SeqSettings::get_conn_string` - see [readme](../README.md#plug-the-seq-logger).
4+
5+
```
6+
url=http://127.0.0.1:5341;apikey=my-api-key;product=my-product;timeout=10
7+
```
8+
9+
`key=value` pairs separated by `;`. The keys are in lower case, with no spaces around them and no `;` after the last pair. Only the first `=` of a pair separates, so a value may have `=` in it - but not `;`.
10+
11+
| Key | | Default | |
12+
|---|---|---|---|
13+
| `url` | required | | Where the records are sent: `{url}/api/events/raw`. An `https://` one needs [TLS turned on](seq-logger.md#https). |
14+
| `apikey` | optional | none | Sent as the `X-Seq-ApiKey` header. |
15+
| `product` | optional | none | The product scope of the records - see [Product scope](#product-scope). |
16+
| `timeout` | optional | `10` | Seconds a request waits for the response. |
17+
| `queuesize` | optional | no limit | How many records wait to be sent; a record over that is dropped. |
18+
| `flushlogschunk` | optional | `50` | A number; read and not used. |
19+
| `flushdelay` | optional | `1` | A number; read and not used. |
20+
21+
## Product scope
22+
23+
my-logger-server keeps and searches the records by product scope. `product` names the scope of everything the application writes: it is sent as the `scope` header with every request.
24+
25+
```
26+
url=http://my-logger-server:8000;product=my-product
27+
```
28+
29+
- A product is `a-z`, `0-9`, `-` and `_`, 64 characters at most. Spaces around it are cut off and upper case is turned to lower case: `product=My-Product` is sent as `my-product`.
30+
- Anything else - an empty value as well - [panics](#a-string-which-can-not-be-parsed). The server ignores a scope it can not use without a word, so the logger does not let one through.
31+
- Without `product` no header is sent. The server then finds the scope by the `Application` of a record, and takes its default scope when none matches.
32+
- The server does not ask for a scope to be set up beforehand: a product with a typo in it becomes a scope of its own.
33+
- Seq itself has no product scopes and takes no notice of the header.
34+
35+
## Changes on the fly
36+
37+
The string is read again before every request, so a change needs no restart:
38+
39+
- `url`, `apikey`, `product` and `timeout` apply from the next request.
40+
- `queuesize` is taken at the start only.
41+
42+
## A string which can not be parsed
43+
44+
| What is wrong | What happens |
45+
|---|---|
46+
| No `url` | Not a panic: `Can not parse Logs settings` is printed to stderr and the string is read again every 5 seconds. Nothing is sent until then. |
47+
| An unknown key - `Url`, `ApiKey` in upper case as well | panic |
48+
| A pair without `=` - a `;` after the last pair as well | panic |
49+
| `timeout`, `queuesize`, `flushlogschunk` or `flushdelay` which is not a number | panic |
50+
| A `product` which can not name a scope | panic |
51+
52+
At the start a panic stops the application, with the reason in stderr.
53+
54+
Do not let the string turn into one which panics while the application is running: nothing is sent and the records written meanwhile are lost until it is fixed.

‎docs/seq-logger.md‎

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Seq logger
2+
3+
A [consumer](consumers.md) which sends the records to [Seq](https://datalust.co/seq) or to my-logger-server. It comes with the `my-seq-logger` feature of `my-logger` as `my_logger::my_seq_logger`. How to plug it: [readme](../README.md#plug-the-seq-logger). What it is set up with: [connection string](seq-connection-string.md).
4+
5+
## Start
6+
7+
`SeqLogger::enable_from_connection_string(settings).await` is called once, at the start of the application, inside a Tokio runtime. It:
8+
9+
1. Sets the panic hook - see [Panics](#panics).
10+
2. Reads the connection string. Without `url` in it the call does not return: it prints `Can not parse Logs settings` and reads the string again every 5 seconds. A string which can not be parsed otherwise [panics](seq-connection-string.md#a-string-which-can-not-be-parsed).
11+
3. Starts the task which sends the records. It runs on the runtime the call is awaited on and sends for as long as that runtime lives.
12+
4. Plugs itself into `my_logger::LOGGER` and prints `Seq Logs writer is started`.
13+
14+
A second call plugs a second sender, and every record is sent twice.
15+
16+
Writing a record needs no runtime: it is put into a queue in memory, from any thread.
17+
18+
## How the records are sent
19+
20+
- The task takes everything there is in the queue and sends it as one request. What is written while a request is on its way goes with the next one.
21+
- A request carries up to 2 MB of records; the rest goes in the requests after it.
22+
- With `queuesize` in the connection string the queue holds that many records, and a record written into a full queue is dropped. Without it the queue has no limit.
23+
24+
The request is `POST {url}/api/events/raw?clef`:
25+
26+
| Header | |
27+
|---|---|
28+
| `Content-Type` | `application/vnd.serilog.clef` |
29+
| `X-Seq-ApiKey` | `apikey` of the connection string, when it is there. |
30+
| `scope` | `product` of the connection string, when it is there - see [Product scope](seq-connection-string.md#product-scope). |
31+
32+
The body is a record per line, the lines separated by `\r\n`:
33+
34+
```json
35+
{"@l":"Info","@t":"2026-10-12T09:24:22.984955","Process":"process_name","@m":"message","Application":"my-app","Version":"0.1.0","orderId":15,"comment":"hello"}
36+
```
37+
38+
| Field | |
39+
|---|---|
40+
| `@l` | `Info`, `Warning`, `Error`, `Fatal` or `Debug`. |
41+
| `@t` | When the record is written, UTC. |
42+
| `Process` | The process of the record. |
43+
| `@m` | The message of the record. |
44+
| the rest | The [parameters of every record](populated-params.md), then the context of the record. A context value which reads as a number, `true`, `false` or `null` is written as such, any other one as a string. |
45+
46+
## When a request fails
47+
48+
A request is tried 4 times, then its records are dropped:
49+
50+
- **No response** - the server is not reachable, or does not answer within `timeout`. Each attempt prints `Attempt: N Error while uploading logs to seq` to stderr, the attempts are a second apart.
51+
- **A status other than 200-209.** The attempts follow one another with no pause and print nothing.
52+
53+
## Panics
54+
55+
`enable_from_connection_string` replaces the panic hook of the process. A panic anywhere in the application is written as a `FatalError` record with the process `Panic Handler`, the panic message as the message, and `Location` and `PanicInfo` in the context.
56+
57+
The record is printed to the console at once and sent in the background as any other one, so a panic which ends the process may not get to the server.
58+
59+
## HTTPS
60+
61+
An `https://` url needs a TLS provider feature of [flurl](https://github.com/MyJetTools/fl-url) - `with-ring-tls` or `with-rust-tls` - turned on by the application; service-sdk has the features of the same names. Without one every request fails with `FlUrl does not support https` in stderr and the records are dropped.
62+
63+
## Environment variables
64+
65+
| Variable | |
66+
|---|---|
67+
| `SEQ_DEBUG` | Set to anything - every request and the response to it are printed to the console. |
68+
| `SEQ_COMPRESS` | Set to anything - a request over 1 MB is sent gzipped. |
69+
| `ENV_INFO` | Sent as `EnvInfo` with every record - see [Parameters of every record](populated-params.md). |

0 commit comments

Comments
 (0)