devlogs 2.2.6__tar.gz → 2.2.8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {devlogs-2.2.6/src/devlogs.egg-info → devlogs-2.2.8}/PKG-INFO +84 -24
- {devlogs-2.2.6 → devlogs-2.2.8}/README.md +83 -23
- {devlogs-2.2.6 → devlogs-2.2.8}/pyproject.toml +1 -1
- {devlogs-2.2.6 → devlogs-2.2.8}/src/devlogs/_version_static.py +1 -1
- {devlogs-2.2.6 → devlogs-2.2.8}/src/devlogs/cli.py +17 -2
- {devlogs-2.2.6 → devlogs-2.2.8}/src/devlogs/collector/__init__.py +18 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/src/devlogs/collector/auth.py +22 -13
- {devlogs-2.2.6 → devlogs-2.2.8}/src/devlogs/collector/cli.py +26 -5
- {devlogs-2.2.6 → devlogs-2.2.8}/src/devlogs/collector/errors.py +13 -0
- devlogs-2.2.8/src/devlogs/collector/plugins.py +189 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/src/devlogs/collector/server.py +75 -5
- {devlogs-2.2.6 → devlogs-2.2.8}/src/devlogs/config.py +12 -7
- {devlogs-2.2.6 → devlogs-2.2.8}/src/devlogs/devlogs_client.py +18 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/src/devlogs/handler.py +16 -1
- {devlogs-2.2.6 → devlogs-2.2.8}/src/devlogs/mcp/server.py +53 -0
- {devlogs-2.2.6 → devlogs-2.2.8/src/devlogs.egg-info}/PKG-INFO +84 -24
- {devlogs-2.2.6 → devlogs-2.2.8}/src/devlogs.egg-info/SOURCES.txt +2 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/tests/test_cli.py +103 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/tests/test_collector_auth.py +46 -28
- devlogs-2.2.8/tests/test_collector_plugins.py +626 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/tests/test_collector_server.py +25 -2
- {devlogs-2.2.6 → devlogs-2.2.8}/tests/test_config.py +54 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/tests/test_devlogs_client.py +134 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/tests/test_handler.py +200 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/tests/test_mcp_server.py +58 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/LICENSE +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/MANIFEST.in +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/setup.cfg +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/src/devlogs/__init__.py +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/src/devlogs/__main__.py +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/src/devlogs/build_info.py +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/src/devlogs/collector/forwarder.py +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/src/devlogs/collector/ingestor.py +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/src/devlogs/collector/schema.py +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/src/devlogs/context.py +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/src/devlogs/demo.py +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/src/devlogs/formatting.py +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/src/devlogs/jenkins/__init__.py +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/src/devlogs/jenkins/cli.py +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/src/devlogs/jenkins/core.py +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/src/devlogs/levels.py +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/src/devlogs/mcp/__init__.py +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/src/devlogs/opensearch/__init__.py +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/src/devlogs/opensearch/client.py +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/src/devlogs/opensearch/indexing.py +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/src/devlogs/opensearch/mappings.py +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/src/devlogs/opensearch/queries.py +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/src/devlogs/retention.py +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/src/devlogs/scrub.py +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/src/devlogs/time_utils.py +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/src/devlogs/version.py +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/src/devlogs/web/__init__.py +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/src/devlogs/web/server.py +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/src/devlogs/web/static/devlogs.css +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/src/devlogs/web/static/devlogs.js +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/src/devlogs/web/static/index.html +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/src/devlogs/wrapper.py +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/src/devlogs.egg-info/dependency_links.txt +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/src/devlogs.egg-info/entry_points.txt +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/src/devlogs.egg-info/requires.txt +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/src/devlogs.egg-info/top_level.txt +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/tests/test_build_info.py +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/tests/test_collector_config.py +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/tests/test_collector_schema.py +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/tests/test_context.py +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/tests/test_formatting.py +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/tests/test_indexing.py +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/tests/test_levels.py +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/tests/test_mappings.py +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/tests/test_opensearch_client.py +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/tests/test_opensearch_queries.py +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/tests/test_retention.py +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/tests/test_scrub.py +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/tests/test_time_utils.py +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/tests/test_url_parsing.py +0 -0
- {devlogs-2.2.6 → devlogs-2.2.8}/tests/test_web.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: devlogs
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.8
|
|
4
4
|
Summary: Developer-focused logging library for Python with OpenSearch integration.
|
|
5
5
|
Author-email: Dan Driscoll <dan@thedandriscoll.org>
|
|
6
6
|
License: MIT License
|
|
@@ -62,35 +62,61 @@ If you don't have OpenSearch running and you want to stand one up:
|
|
|
62
62
|
|
|
63
63
|
## Step 2: Copy/paste these instructions into your coding agent
|
|
64
64
|
|
|
65
|
+
### Python
|
|
66
|
+
|
|
65
67
|
> Please do the following in this project:
|
|
66
68
|
> 1. Install `devlogs` as a dev dependency (`pip install devlogs` or add it to `requirements-dev.txt`/`pyproject.toml` optional dependencies).
|
|
67
|
-
> 2.
|
|
68
|
-
>
|
|
69
|
+
> 2. Create a `.env.devlogs` file in the project root with a single `DEVLOGS_URL` variable (devlogs auto-discovers this file). Use the `opensearchs://` scheme for TLS or `opensearch://` for non-TLS:
|
|
70
|
+
> ```
|
|
71
|
+
> DEVLOGS_URL=opensearchs://admin:YourPasswordHere@localhost:9200/devlogs-<projectname>
|
|
72
|
+
> ```
|
|
73
|
+
> 3. Run `devlogs init` (inside the virtualenv if one is set up) and verify the index is healthy.
|
|
74
|
+
> 4. Add devlogs hooks at the beginning of the application (main entrypoint/startup module), wrapped in an environment check so it only runs in development:
|
|
69
75
|
> ```python
|
|
70
76
|
> import os
|
|
71
77
|
> import logging
|
|
72
78
|
> if os.getenv("ENVIRONMENT") != "production":
|
|
73
79
|
> from devlogs.handler import DevlogsHandler
|
|
80
|
+
> from devlogs.opensearch.client import get_opensearch_client
|
|
74
81
|
> from devlogs.build_info import resolve_build_info
|
|
75
82
|
>
|
|
76
|
-
> # Resolve build info (reads .build.json or generates)
|
|
77
83
|
> build_info = resolve_build_info(write_if_missing=True)
|
|
78
|
-
>
|
|
79
84
|
> handler = DevlogsHandler(
|
|
80
85
|
> application="my-app", # Required: your app name
|
|
81
86
|
> component="api", # Required: component name
|
|
82
87
|
> level=logging.INFO,
|
|
88
|
+
> opensearch_client=get_opensearch_client(),
|
|
83
89
|
> version=build_info.build_id,
|
|
84
90
|
> )
|
|
85
91
|
> logging.getLogger().addHandler(handler)
|
|
86
92
|
> logging.getLogger().setLevel(logging.INFO)
|
|
87
|
-
>
|
|
88
|
-
> # Log startup
|
|
89
93
|
> logging.info("App started")
|
|
90
94
|
> ```
|
|
91
|
-
> 4. Run `devlogs init` (inside the virtualenv if one is set up) and verify the index is healthy.
|
|
92
95
|
> 5. Ask the user if they want MCP set up; if yes, state which agent you are (`copilot`, `claude`, or `codex`) and run `devlogs initmcp <agent>`.
|
|
93
96
|
|
|
97
|
+
### JavaScript / TypeScript (browser)
|
|
98
|
+
|
|
99
|
+
> Please do the following in this project:
|
|
100
|
+
> 1. Install the browser package as a dev dependency: `npm install --save-dev devlogs-browser`.
|
|
101
|
+
> 2. Initialize devlogs early in the application entrypoint, wrapped in an environment check so it only runs in development:
|
|
102
|
+
> ```javascript
|
|
103
|
+
> import * as devlogs from 'devlogs-browser';
|
|
104
|
+
>
|
|
105
|
+
> if (process.env.NODE_ENV === 'development') {
|
|
106
|
+
> devlogs.init({
|
|
107
|
+
> url: 'https://admin:YourPasswordHere@localhost:9200',
|
|
108
|
+
> index: 'devlogs-<projectname>',
|
|
109
|
+
> application: 'my-app', // Required: your app name
|
|
110
|
+
> component: 'frontend', // Required: component name
|
|
111
|
+
> });
|
|
112
|
+
> }
|
|
113
|
+
> ```
|
|
114
|
+
> After `init()`, all `console.log`, `console.warn`, `console.error`, and `console.debug` calls are automatically forwarded to OpenSearch. The original console output is preserved.
|
|
115
|
+
> 3. Use `devlogs.setArea('dashboard')` and `devlogs.setOperationId('op-123')` to add context to logs. Pass a plain object as the last argument to attach custom fields:
|
|
116
|
+
> ```javascript
|
|
117
|
+
> console.log('User action', { userId: 123, action: 'clicked' });
|
|
118
|
+
> ```
|
|
119
|
+
|
|
94
120
|
## Step 3: Use devlogs
|
|
95
121
|
|
|
96
122
|
1. Run `devlogs initmcp <agent>` to set up the MCP server.
|
|
@@ -108,14 +134,28 @@ If you don't have OpenSearch running and you want to stand one up:
|
|
|
108
134
|
```sh
|
|
109
135
|
docker-compose up -d opensearch
|
|
110
136
|
```
|
|
111
|
-
Or point `
|
|
137
|
+
Or point `DEVLOGS_URL` at an existing cluster.
|
|
112
138
|
|
|
113
|
-
3. **
|
|
139
|
+
3. **Configure connection** (choose one):
|
|
140
|
+
|
|
141
|
+
Option A — `.env.devlogs` file (auto-discovered):
|
|
142
|
+
```
|
|
143
|
+
DEVLOGS_URL=opensearchs://admin:YourPasswordHere@localhost:9200/devlogs-myproject
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Option B — `--url` flag (no config file needed):
|
|
147
|
+
```sh
|
|
148
|
+
devlogs --url 'opensearchs://admin:pass@localhost:9200/devlogs-myproject' init
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Use `devlogs mkurl` to interactively build a properly URL-encoded connection string (handy for passwords with special characters).
|
|
152
|
+
|
|
153
|
+
4. **Initialize indices/templates:**
|
|
114
154
|
```sh
|
|
115
155
|
devlogs init
|
|
116
156
|
```
|
|
117
157
|
|
|
118
|
-
|
|
158
|
+
5. **Use in Python code (development only):**
|
|
119
159
|
```python
|
|
120
160
|
import os
|
|
121
161
|
import logging
|
|
@@ -123,15 +163,15 @@ If you don't have OpenSearch running and you want to stand one up:
|
|
|
123
163
|
# Only enable devlogs in development
|
|
124
164
|
if os.getenv("ENVIRONMENT") != "production":
|
|
125
165
|
from devlogs.handler import DevlogsHandler
|
|
166
|
+
from devlogs.opensearch.client import get_opensearch_client
|
|
126
167
|
from devlogs.build_info import resolve_build_info
|
|
127
168
|
|
|
128
|
-
# Get build info (reads .build.json or generates)
|
|
129
169
|
build_info = resolve_build_info(write_if_missing=True)
|
|
130
|
-
|
|
131
170
|
handler = DevlogsHandler(
|
|
132
171
|
application="my-app",
|
|
133
172
|
component="default",
|
|
134
173
|
level=logging.DEBUG,
|
|
174
|
+
opensearch_client=get_opensearch_client(),
|
|
135
175
|
version=build_info.build_id,
|
|
136
176
|
)
|
|
137
177
|
logging.getLogger().addHandler(handler)
|
|
@@ -140,17 +180,38 @@ If you don't have OpenSearch running and you want to stand one up:
|
|
|
140
180
|
logging.info("Hello from devlogs!")
|
|
141
181
|
```
|
|
142
182
|
|
|
143
|
-
|
|
183
|
+
6. **Use in JavaScript/TypeScript (browser, development only):**
|
|
184
|
+
```javascript
|
|
185
|
+
import * as devlogs from 'devlogs-browser';
|
|
186
|
+
|
|
187
|
+
if (process.env.NODE_ENV === 'development') {
|
|
188
|
+
devlogs.init({
|
|
189
|
+
url: 'https://admin:YourPasswordHere@localhost:9200',
|
|
190
|
+
index: 'devlogs-myproject',
|
|
191
|
+
application: 'my-app',
|
|
192
|
+
component: 'frontend',
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// All console methods are now forwarded to OpenSearch
|
|
197
|
+
console.log('Hello from devlogs!');
|
|
198
|
+
|
|
199
|
+
// Add context
|
|
200
|
+
devlogs.setArea('dashboard');
|
|
201
|
+
console.log('User action', { userId: 123, action: 'clicked' });
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
7. **Tail logs from CLI:**
|
|
144
205
|
```sh
|
|
145
206
|
devlogs tail --area web --follow
|
|
146
207
|
```
|
|
147
208
|
|
|
148
|
-
|
|
209
|
+
8. **Search logs from CLI:**
|
|
149
210
|
```sh
|
|
150
211
|
devlogs search --q "error" --area web
|
|
151
212
|
```
|
|
152
213
|
|
|
153
|
-
|
|
214
|
+
9. **Run the web UI:**
|
|
154
215
|
```sh
|
|
155
216
|
uvicorn devlogs.web.server:app --port 8088
|
|
156
217
|
# Then open http://localhost:8088/ui/
|
|
@@ -224,7 +285,7 @@ client.emit(
|
|
|
224
285
|
|
|
225
286
|
```bash
|
|
226
287
|
docker build -f Dockerfile.collector -t devlogs-collector .
|
|
227
|
-
docker run -p 8080:8080 -e
|
|
288
|
+
docker run -p 8080:8080 -e DEVLOGS_URL=opensearchs://admin:pass@opensearch:9200/devlogs devlogs-collector
|
|
228
289
|
```
|
|
229
290
|
|
|
230
291
|
See [HOWTO-COLLECTOR.md](HOWTO-COLLECTOR.md) for complete collector documentation.
|
|
@@ -260,7 +321,7 @@ Stream Jenkins build logs to OpenSearch using a standalone binary:
|
|
|
260
321
|
pipeline {
|
|
261
322
|
agent any
|
|
262
323
|
environment {
|
|
263
|
-
|
|
324
|
+
DEVLOGS_URL = credentials('devlogs-url')
|
|
264
325
|
DEVLOGS_BINARY_URL = credentials('devlogs-binary-url')
|
|
265
326
|
}
|
|
266
327
|
stages {
|
|
@@ -284,13 +345,12 @@ Build the binary with `./build-standalone.sh` and host it somewhere accessible.
|
|
|
284
345
|
|
|
285
346
|
### Environment Variables
|
|
286
347
|
|
|
287
|
-
**
|
|
288
|
-
- `DEVLOGS_URL` -
|
|
348
|
+
**Connection (choose one):**
|
|
349
|
+
- `DEVLOGS_URL` - Standard connection URL with auto-detection. OpenSearch URLs (`opensearchs://`, `opensearch://`) connect directly; collector URLs (`http://`, `https://`) use the collector endpoint.
|
|
289
350
|
- `DEVLOGS_FORWARD_URL` - Forward mode: proxy to this upstream URL
|
|
290
351
|
|
|
291
|
-
**OpenSearch
|
|
352
|
+
**OpenSearch (individual variables, alternative to DEVLOGS_URL):**
|
|
292
353
|
- `DEVLOGS_OPENSEARCH_HOST`, `DEVLOGS_OPENSEARCH_PORT`, `DEVLOGS_OPENSEARCH_USER`, `DEVLOGS_OPENSEARCH_PASS`
|
|
293
|
-
- `DEVLOGS_OPENSEARCH_URL` - URL shortcut (e.g., `https://user:pass@host:9200/index`)
|
|
294
354
|
- `DEVLOGS_OPENSEARCH_VERIFY_CERTS`, `DEVLOGS_OPENSEARCH_CA_CERT` - SSL/TLS settings
|
|
295
355
|
|
|
296
356
|
**Index & Retention:**
|
|
@@ -308,7 +368,7 @@ See [.env.example](.env.example) for a complete configuration template.
|
|
|
308
368
|
Use `--url` to specify connection details without a `.env` file:
|
|
309
369
|
|
|
310
370
|
```bash
|
|
311
|
-
devlogs --url '
|
|
371
|
+
devlogs --url 'opensearchs://admin:pass@host:9200/myindex' tail
|
|
312
372
|
```
|
|
313
373
|
|
|
314
374
|
Use `--env` to load from a specific `.env` file:
|
|
@@ -325,7 +385,7 @@ Use `devlogs mkurl` to interactively create a properly URL-encoded connection st
|
|
|
325
385
|
devlogs mkurl
|
|
326
386
|
# Outputs the URL in three formats:
|
|
327
387
|
# 1. Bare URL (for --url flag)
|
|
328
|
-
# 2. Single
|
|
388
|
+
# 2. Single DEVLOGS_URL variable
|
|
329
389
|
# 3. Individual .env variables
|
|
330
390
|
```
|
|
331
391
|
|
|
@@ -19,35 +19,61 @@ If you don't have OpenSearch running and you want to stand one up:
|
|
|
19
19
|
|
|
20
20
|
## Step 2: Copy/paste these instructions into your coding agent
|
|
21
21
|
|
|
22
|
+
### Python
|
|
23
|
+
|
|
22
24
|
> Please do the following in this project:
|
|
23
25
|
> 1. Install `devlogs` as a dev dependency (`pip install devlogs` or add it to `requirements-dev.txt`/`pyproject.toml` optional dependencies).
|
|
24
|
-
> 2.
|
|
25
|
-
>
|
|
26
|
+
> 2. Create a `.env.devlogs` file in the project root with a single `DEVLOGS_URL` variable (devlogs auto-discovers this file). Use the `opensearchs://` scheme for TLS or `opensearch://` for non-TLS:
|
|
27
|
+
> ```
|
|
28
|
+
> DEVLOGS_URL=opensearchs://admin:YourPasswordHere@localhost:9200/devlogs-<projectname>
|
|
29
|
+
> ```
|
|
30
|
+
> 3. Run `devlogs init` (inside the virtualenv if one is set up) and verify the index is healthy.
|
|
31
|
+
> 4. Add devlogs hooks at the beginning of the application (main entrypoint/startup module), wrapped in an environment check so it only runs in development:
|
|
26
32
|
> ```python
|
|
27
33
|
> import os
|
|
28
34
|
> import logging
|
|
29
35
|
> if os.getenv("ENVIRONMENT") != "production":
|
|
30
36
|
> from devlogs.handler import DevlogsHandler
|
|
37
|
+
> from devlogs.opensearch.client import get_opensearch_client
|
|
31
38
|
> from devlogs.build_info import resolve_build_info
|
|
32
39
|
>
|
|
33
|
-
> # Resolve build info (reads .build.json or generates)
|
|
34
40
|
> build_info = resolve_build_info(write_if_missing=True)
|
|
35
|
-
>
|
|
36
41
|
> handler = DevlogsHandler(
|
|
37
42
|
> application="my-app", # Required: your app name
|
|
38
43
|
> component="api", # Required: component name
|
|
39
44
|
> level=logging.INFO,
|
|
45
|
+
> opensearch_client=get_opensearch_client(),
|
|
40
46
|
> version=build_info.build_id,
|
|
41
47
|
> )
|
|
42
48
|
> logging.getLogger().addHandler(handler)
|
|
43
49
|
> logging.getLogger().setLevel(logging.INFO)
|
|
44
|
-
>
|
|
45
|
-
> # Log startup
|
|
46
50
|
> logging.info("App started")
|
|
47
51
|
> ```
|
|
48
|
-
> 4. Run `devlogs init` (inside the virtualenv if one is set up) and verify the index is healthy.
|
|
49
52
|
> 5. Ask the user if they want MCP set up; if yes, state which agent you are (`copilot`, `claude`, or `codex`) and run `devlogs initmcp <agent>`.
|
|
50
53
|
|
|
54
|
+
### JavaScript / TypeScript (browser)
|
|
55
|
+
|
|
56
|
+
> Please do the following in this project:
|
|
57
|
+
> 1. Install the browser package as a dev dependency: `npm install --save-dev devlogs-browser`.
|
|
58
|
+
> 2. Initialize devlogs early in the application entrypoint, wrapped in an environment check so it only runs in development:
|
|
59
|
+
> ```javascript
|
|
60
|
+
> import * as devlogs from 'devlogs-browser';
|
|
61
|
+
>
|
|
62
|
+
> if (process.env.NODE_ENV === 'development') {
|
|
63
|
+
> devlogs.init({
|
|
64
|
+
> url: 'https://admin:YourPasswordHere@localhost:9200',
|
|
65
|
+
> index: 'devlogs-<projectname>',
|
|
66
|
+
> application: 'my-app', // Required: your app name
|
|
67
|
+
> component: 'frontend', // Required: component name
|
|
68
|
+
> });
|
|
69
|
+
> }
|
|
70
|
+
> ```
|
|
71
|
+
> After `init()`, all `console.log`, `console.warn`, `console.error`, and `console.debug` calls are automatically forwarded to OpenSearch. The original console output is preserved.
|
|
72
|
+
> 3. Use `devlogs.setArea('dashboard')` and `devlogs.setOperationId('op-123')` to add context to logs. Pass a plain object as the last argument to attach custom fields:
|
|
73
|
+
> ```javascript
|
|
74
|
+
> console.log('User action', { userId: 123, action: 'clicked' });
|
|
75
|
+
> ```
|
|
76
|
+
|
|
51
77
|
## Step 3: Use devlogs
|
|
52
78
|
|
|
53
79
|
1. Run `devlogs initmcp <agent>` to set up the MCP server.
|
|
@@ -65,14 +91,28 @@ If you don't have OpenSearch running and you want to stand one up:
|
|
|
65
91
|
```sh
|
|
66
92
|
docker-compose up -d opensearch
|
|
67
93
|
```
|
|
68
|
-
Or point `
|
|
94
|
+
Or point `DEVLOGS_URL` at an existing cluster.
|
|
69
95
|
|
|
70
|
-
3. **
|
|
96
|
+
3. **Configure connection** (choose one):
|
|
97
|
+
|
|
98
|
+
Option A — `.env.devlogs` file (auto-discovered):
|
|
99
|
+
```
|
|
100
|
+
DEVLOGS_URL=opensearchs://admin:YourPasswordHere@localhost:9200/devlogs-myproject
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Option B — `--url` flag (no config file needed):
|
|
104
|
+
```sh
|
|
105
|
+
devlogs --url 'opensearchs://admin:pass@localhost:9200/devlogs-myproject' init
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Use `devlogs mkurl` to interactively build a properly URL-encoded connection string (handy for passwords with special characters).
|
|
109
|
+
|
|
110
|
+
4. **Initialize indices/templates:**
|
|
71
111
|
```sh
|
|
72
112
|
devlogs init
|
|
73
113
|
```
|
|
74
114
|
|
|
75
|
-
|
|
115
|
+
5. **Use in Python code (development only):**
|
|
76
116
|
```python
|
|
77
117
|
import os
|
|
78
118
|
import logging
|
|
@@ -80,15 +120,15 @@ If you don't have OpenSearch running and you want to stand one up:
|
|
|
80
120
|
# Only enable devlogs in development
|
|
81
121
|
if os.getenv("ENVIRONMENT") != "production":
|
|
82
122
|
from devlogs.handler import DevlogsHandler
|
|
123
|
+
from devlogs.opensearch.client import get_opensearch_client
|
|
83
124
|
from devlogs.build_info import resolve_build_info
|
|
84
125
|
|
|
85
|
-
# Get build info (reads .build.json or generates)
|
|
86
126
|
build_info = resolve_build_info(write_if_missing=True)
|
|
87
|
-
|
|
88
127
|
handler = DevlogsHandler(
|
|
89
128
|
application="my-app",
|
|
90
129
|
component="default",
|
|
91
130
|
level=logging.DEBUG,
|
|
131
|
+
opensearch_client=get_opensearch_client(),
|
|
92
132
|
version=build_info.build_id,
|
|
93
133
|
)
|
|
94
134
|
logging.getLogger().addHandler(handler)
|
|
@@ -97,17 +137,38 @@ If you don't have OpenSearch running and you want to stand one up:
|
|
|
97
137
|
logging.info("Hello from devlogs!")
|
|
98
138
|
```
|
|
99
139
|
|
|
100
|
-
|
|
140
|
+
6. **Use in JavaScript/TypeScript (browser, development only):**
|
|
141
|
+
```javascript
|
|
142
|
+
import * as devlogs from 'devlogs-browser';
|
|
143
|
+
|
|
144
|
+
if (process.env.NODE_ENV === 'development') {
|
|
145
|
+
devlogs.init({
|
|
146
|
+
url: 'https://admin:YourPasswordHere@localhost:9200',
|
|
147
|
+
index: 'devlogs-myproject',
|
|
148
|
+
application: 'my-app',
|
|
149
|
+
component: 'frontend',
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// All console methods are now forwarded to OpenSearch
|
|
154
|
+
console.log('Hello from devlogs!');
|
|
155
|
+
|
|
156
|
+
// Add context
|
|
157
|
+
devlogs.setArea('dashboard');
|
|
158
|
+
console.log('User action', { userId: 123, action: 'clicked' });
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
7. **Tail logs from CLI:**
|
|
101
162
|
```sh
|
|
102
163
|
devlogs tail --area web --follow
|
|
103
164
|
```
|
|
104
165
|
|
|
105
|
-
|
|
166
|
+
8. **Search logs from CLI:**
|
|
106
167
|
```sh
|
|
107
168
|
devlogs search --q "error" --area web
|
|
108
169
|
```
|
|
109
170
|
|
|
110
|
-
|
|
171
|
+
9. **Run the web UI:**
|
|
111
172
|
```sh
|
|
112
173
|
uvicorn devlogs.web.server:app --port 8088
|
|
113
174
|
# Then open http://localhost:8088/ui/
|
|
@@ -181,7 +242,7 @@ client.emit(
|
|
|
181
242
|
|
|
182
243
|
```bash
|
|
183
244
|
docker build -f Dockerfile.collector -t devlogs-collector .
|
|
184
|
-
docker run -p 8080:8080 -e
|
|
245
|
+
docker run -p 8080:8080 -e DEVLOGS_URL=opensearchs://admin:pass@opensearch:9200/devlogs devlogs-collector
|
|
185
246
|
```
|
|
186
247
|
|
|
187
248
|
See [HOWTO-COLLECTOR.md](HOWTO-COLLECTOR.md) for complete collector documentation.
|
|
@@ -217,7 +278,7 @@ Stream Jenkins build logs to OpenSearch using a standalone binary:
|
|
|
217
278
|
pipeline {
|
|
218
279
|
agent any
|
|
219
280
|
environment {
|
|
220
|
-
|
|
281
|
+
DEVLOGS_URL = credentials('devlogs-url')
|
|
221
282
|
DEVLOGS_BINARY_URL = credentials('devlogs-binary-url')
|
|
222
283
|
}
|
|
223
284
|
stages {
|
|
@@ -241,13 +302,12 @@ Build the binary with `./build-standalone.sh` and host it somewhere accessible.
|
|
|
241
302
|
|
|
242
303
|
### Environment Variables
|
|
243
304
|
|
|
244
|
-
**
|
|
245
|
-
- `DEVLOGS_URL` -
|
|
305
|
+
**Connection (choose one):**
|
|
306
|
+
- `DEVLOGS_URL` - Standard connection URL with auto-detection. OpenSearch URLs (`opensearchs://`, `opensearch://`) connect directly; collector URLs (`http://`, `https://`) use the collector endpoint.
|
|
246
307
|
- `DEVLOGS_FORWARD_URL` - Forward mode: proxy to this upstream URL
|
|
247
308
|
|
|
248
|
-
**OpenSearch
|
|
309
|
+
**OpenSearch (individual variables, alternative to DEVLOGS_URL):**
|
|
249
310
|
- `DEVLOGS_OPENSEARCH_HOST`, `DEVLOGS_OPENSEARCH_PORT`, `DEVLOGS_OPENSEARCH_USER`, `DEVLOGS_OPENSEARCH_PASS`
|
|
250
|
-
- `DEVLOGS_OPENSEARCH_URL` - URL shortcut (e.g., `https://user:pass@host:9200/index`)
|
|
251
311
|
- `DEVLOGS_OPENSEARCH_VERIFY_CERTS`, `DEVLOGS_OPENSEARCH_CA_CERT` - SSL/TLS settings
|
|
252
312
|
|
|
253
313
|
**Index & Retention:**
|
|
@@ -265,7 +325,7 @@ See [.env.example](.env.example) for a complete configuration template.
|
|
|
265
325
|
Use `--url` to specify connection details without a `.env` file:
|
|
266
326
|
|
|
267
327
|
```bash
|
|
268
|
-
devlogs --url '
|
|
328
|
+
devlogs --url 'opensearchs://admin:pass@host:9200/myindex' tail
|
|
269
329
|
```
|
|
270
330
|
|
|
271
331
|
Use `--env` to load from a specific `.env` file:
|
|
@@ -282,7 +342,7 @@ Use `devlogs mkurl` to interactively create a properly URL-encoded connection st
|
|
|
282
342
|
devlogs mkurl
|
|
283
343
|
# Outputs the URL in three formats:
|
|
284
344
|
# 1. Bare URL (for --url flag)
|
|
285
|
-
# 2. Single
|
|
345
|
+
# 2. Single DEVLOGS_URL variable
|
|
286
346
|
# 3. Individual .env variables
|
|
287
347
|
```
|
|
288
348
|
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
# AUTO-GENERATED at build time — do not edit or commit
|
|
2
|
-
__version__ = "2.2.
|
|
2
|
+
__version__ = "2.2.8"
|
|
@@ -600,14 +600,29 @@ def diagnose(
|
|
|
600
600
|
else:
|
|
601
601
|
_emit("warn", ".env: not found and no DEVLOGS_* settings detected")
|
|
602
602
|
|
|
603
|
+
# Plugin / collector mode check
|
|
604
|
+
if cfg.url_mode == "collector":
|
|
605
|
+
from .collector.plugins import get_plugin_for_url
|
|
606
|
+
plugin = get_plugin_for_url(cfg.collector_url, cfg)
|
|
607
|
+
if plugin:
|
|
608
|
+
_emit("ok", f"Mode: plugin ({plugin.name})")
|
|
609
|
+
try:
|
|
610
|
+
status = plugin.check()
|
|
611
|
+
_emit("ok", f"Plugin: {status}")
|
|
612
|
+
except Exception as e:
|
|
613
|
+
_emit("error", f"Plugin: {e}")
|
|
614
|
+
else:
|
|
615
|
+
_emit("ok", f"Mode: collector ({cfg.collector_url})")
|
|
616
|
+
|
|
603
617
|
client = None
|
|
604
618
|
connection_ok = False
|
|
619
|
+
os_severity = "warn" if cfg.url_mode == "collector" else "error"
|
|
605
620
|
try:
|
|
606
621
|
client = get_opensearch_client()
|
|
607
622
|
except DevlogsDisabledError as exc:
|
|
608
|
-
_emit(
|
|
623
|
+
_emit(os_severity, f"OpenSearch: {exc}")
|
|
609
624
|
except OpenSearchError as exc:
|
|
610
|
-
_emit(
|
|
625
|
+
_emit(os_severity, f"OpenSearch: {exc}")
|
|
611
626
|
else:
|
|
612
627
|
try:
|
|
613
628
|
check_connection(client)
|
|
@@ -13,6 +13,7 @@ from .errors import (
|
|
|
13
13
|
ValidationError,
|
|
14
14
|
ForwardError,
|
|
15
15
|
IngestError,
|
|
16
|
+
PluginError,
|
|
16
17
|
ConfigurationError,
|
|
17
18
|
error_response,
|
|
18
19
|
map_upstream_error,
|
|
@@ -22,8 +23,17 @@ from .errors import (
|
|
|
22
23
|
ERROR_INVALID_PAYLOAD,
|
|
23
24
|
ERROR_FORWARD_FAILED,
|
|
24
25
|
ERROR_INGEST_FAILED,
|
|
26
|
+
ERROR_PLUGIN_FAILED,
|
|
25
27
|
ERROR_NOT_CONFIGURED,
|
|
26
28
|
)
|
|
29
|
+
from .plugins import (
|
|
30
|
+
OutputPlugin,
|
|
31
|
+
register_plugin,
|
|
32
|
+
get_plugin_for_url,
|
|
33
|
+
get_registered_schemes,
|
|
34
|
+
list_plugins,
|
|
35
|
+
dict_to_record,
|
|
36
|
+
)
|
|
27
37
|
|
|
28
38
|
__all__ = [
|
|
29
39
|
"DevlogsRecord",
|
|
@@ -34,6 +44,7 @@ __all__ = [
|
|
|
34
44
|
"ValidationError",
|
|
35
45
|
"ForwardError",
|
|
36
46
|
"IngestError",
|
|
47
|
+
"PluginError",
|
|
37
48
|
"ConfigurationError",
|
|
38
49
|
"CollectorError",
|
|
39
50
|
"error_response",
|
|
@@ -44,5 +55,12 @@ __all__ = [
|
|
|
44
55
|
"ERROR_INVALID_PAYLOAD",
|
|
45
56
|
"ERROR_FORWARD_FAILED",
|
|
46
57
|
"ERROR_INGEST_FAILED",
|
|
58
|
+
"ERROR_PLUGIN_FAILED",
|
|
47
59
|
"ERROR_NOT_CONFIGURED",
|
|
60
|
+
"OutputPlugin",
|
|
61
|
+
"register_plugin",
|
|
62
|
+
"get_plugin_for_url",
|
|
63
|
+
"get_registered_schemes",
|
|
64
|
+
"list_plugins",
|
|
65
|
+
"dict_to_record",
|
|
48
66
|
]
|
|
@@ -15,7 +15,6 @@ from urllib.parse import unquote
|
|
|
15
15
|
TOKEN_PATTERN = re.compile(r"^.{8,}$")
|
|
16
16
|
|
|
17
17
|
# Auth header patterns
|
|
18
|
-
DEVLOGS1_PATTERN = re.compile(r"^Devlogs1\s+(\S+)", re.IGNORECASE)
|
|
19
18
|
BEARER_PATTERN = re.compile(r"^Bearer\s+(\S+)", re.IGNORECASE)
|
|
20
19
|
|
|
21
20
|
# Auth modes
|
|
@@ -117,44 +116,54 @@ def is_token_well_formed(token: Optional[str]) -> bool:
|
|
|
117
116
|
return bool(TOKEN_PATTERN.match(token))
|
|
118
117
|
|
|
119
118
|
|
|
120
|
-
def
|
|
119
|
+
def extract_token(
|
|
121
120
|
authorization: Optional[str] = None,
|
|
122
121
|
x_devlogs_token: Optional[str] = None,
|
|
122
|
+
url_userinfo: Optional[str] = None,
|
|
123
|
+
url_query_token: Optional[str] = None,
|
|
123
124
|
) -> Tuple[Optional[str], str]:
|
|
124
|
-
"""Extract token from request
|
|
125
|
+
"""Extract token from request with proper precedence.
|
|
125
126
|
|
|
126
|
-
Precedence:
|
|
127
|
+
Precedence: Bearer header → X-Devlogs-Token header → URL userinfo → ?token= query
|
|
127
128
|
|
|
128
129
|
Args:
|
|
129
130
|
authorization: The Authorization header value
|
|
130
131
|
x_devlogs_token: The X-Devlogs-Token header value
|
|
132
|
+
url_userinfo: Username from URL (e.g. token@host)
|
|
133
|
+
url_query_token: The ?token= query parameter value
|
|
131
134
|
|
|
132
135
|
Returns:
|
|
133
136
|
Tuple of (token_value, source) where source is one of:
|
|
134
|
-
'
|
|
137
|
+
'bearer', 'x-devlogs-token', 'url-userinfo', 'url-query', or 'none'
|
|
135
138
|
"""
|
|
136
139
|
if authorization:
|
|
137
140
|
authorization = authorization.strip()
|
|
138
|
-
|
|
139
|
-
# Try Devlogs1 first
|
|
140
|
-
match = DEVLOGS1_PATTERN.match(authorization)
|
|
141
|
-
if match:
|
|
142
|
-
return match.group(1), "devlogs1"
|
|
143
|
-
|
|
144
|
-
# Try Bearer
|
|
145
141
|
match = BEARER_PATTERN.match(authorization)
|
|
146
142
|
if match:
|
|
147
143
|
return match.group(1), "bearer"
|
|
148
144
|
|
|
149
|
-
# Fall back to X-Devlogs-Token
|
|
150
145
|
if x_devlogs_token:
|
|
151
146
|
token = x_devlogs_token.strip()
|
|
152
147
|
if token:
|
|
153
148
|
return token, "x-devlogs-token"
|
|
154
149
|
|
|
150
|
+
if url_userinfo:
|
|
151
|
+
token = unquote(url_userinfo.strip())
|
|
152
|
+
if token:
|
|
153
|
+
return token, "url-userinfo"
|
|
154
|
+
|
|
155
|
+
if url_query_token:
|
|
156
|
+
token = unquote(url_query_token.strip())
|
|
157
|
+
if token:
|
|
158
|
+
return token, "url-query"
|
|
159
|
+
|
|
155
160
|
return None, "none"
|
|
156
161
|
|
|
157
162
|
|
|
163
|
+
# Keep old name as alias for backwards compatibility
|
|
164
|
+
extract_token_from_headers = extract_token
|
|
165
|
+
|
|
166
|
+
|
|
158
167
|
def parse_token_map_kv(kv_string: Optional[str]) -> Dict[str, TokenMapping]:
|
|
159
168
|
"""Parse the DEVLOGS_TOKEN_MAP_KV environment variable.
|
|
160
169
|
|
|
@@ -67,11 +67,17 @@ def serve(
|
|
|
67
67
|
fg=typer.colors.YELLOW
|
|
68
68
|
))
|
|
69
69
|
else:
|
|
70
|
-
mode_str = "FORWARD" if mode == "forward" else "INGEST"
|
|
71
|
-
typer.echo(typer.style(f"Starting collector in {mode_str} mode", fg=typer.colors.GREEN))
|
|
72
70
|
if mode == "forward":
|
|
73
|
-
|
|
71
|
+
from .plugins import get_plugin_for_url
|
|
72
|
+
plugin = get_plugin_for_url(cfg.forward_url, cfg)
|
|
73
|
+
if plugin:
|
|
74
|
+
typer.echo(typer.style(f"Starting collector in PLUGIN mode ({plugin.name})", fg=typer.colors.GREEN))
|
|
75
|
+
typer.echo(f" {plugin.display_info()}")
|
|
76
|
+
else:
|
|
77
|
+
typer.echo(typer.style("Starting collector in FORWARD mode", fg=typer.colors.GREEN))
|
|
78
|
+
typer.echo(f" Forwarding to: {cfg.forward_url}")
|
|
74
79
|
else:
|
|
80
|
+
typer.echo(typer.style("Starting collector in INGEST mode", fg=typer.colors.GREEN))
|
|
75
81
|
typer.echo(f" OpenSearch: {cfg.opensearch_host}:{cfg.opensearch_port}")
|
|
76
82
|
typer.echo(f" Index: {cfg.index}")
|
|
77
83
|
|
|
@@ -107,13 +113,20 @@ def check():
|
|
|
107
113
|
typer.echo()
|
|
108
114
|
|
|
109
115
|
# Show mode
|
|
116
|
+
plugin = None
|
|
110
117
|
if mode == "error":
|
|
111
118
|
typer.echo(typer.style("Mode: NOT CONFIGURED", fg=typer.colors.RED))
|
|
112
119
|
typer.echo(" Set DEVLOGS_FORWARD_URL or DEVLOGS_OPENSEARCH_* variables")
|
|
113
120
|
raise typer.Exit(1)
|
|
114
121
|
elif mode == "forward":
|
|
115
|
-
|
|
116
|
-
|
|
122
|
+
from .plugins import get_plugin_for_url
|
|
123
|
+
plugin = get_plugin_for_url(cfg.forward_url, cfg)
|
|
124
|
+
if plugin:
|
|
125
|
+
typer.echo(typer.style(f"Mode: PLUGIN ({plugin.name})", fg=typer.colors.CYAN))
|
|
126
|
+
typer.echo(f" {plugin.display_info()}")
|
|
127
|
+
else:
|
|
128
|
+
typer.echo(typer.style("Mode: FORWARD", fg=typer.colors.CYAN))
|
|
129
|
+
typer.echo(f" Forward URL: {cfg.forward_url}")
|
|
117
130
|
else:
|
|
118
131
|
typer.echo(typer.style("Mode: INGEST", fg=typer.colors.CYAN))
|
|
119
132
|
typer.echo(f" OpenSearch Host: {cfg.opensearch_host}")
|
|
@@ -146,6 +159,14 @@ def check():
|
|
|
146
159
|
except Exception as e:
|
|
147
160
|
typer.echo(typer.style(f" OpenSearch: FAILED - {e}", fg=typer.colors.RED))
|
|
148
161
|
raise typer.Exit(1)
|
|
162
|
+
elif plugin:
|
|
163
|
+
typer.echo(f"Testing {plugin.name} connectivity...")
|
|
164
|
+
try:
|
|
165
|
+
status = plugin.check()
|
|
166
|
+
typer.echo(typer.style(f" {status}", fg=typer.colors.GREEN))
|
|
167
|
+
except Exception as e:
|
|
168
|
+
typer.echo(typer.style(f" Plugin check failed: {e}", fg=typer.colors.RED))
|
|
169
|
+
raise typer.Exit(1)
|
|
149
170
|
else:
|
|
150
171
|
typer.echo("Testing forward URL connectivity...")
|
|
151
172
|
try:
|