Skip to content

Commit b5b452c

Browse files
committed
Adds structured logging with LogTape
Replaces console.* calls with LogTape-based structured logging featuring timestamped output, module-specific categories, and configurable log levels. Introduces module-specific loggers (app, browser, scheduler, http, webhook, etc.) that provide hierarchical categorization and consistent formatting across the codebase. Adds LOG_LEVEL environment variable and debug_logging add-on option to control verbosity from trace to fatal levels, enabling better debugging without code changes. Improves observability by making log patterns more structured and searchable, while maintaining zero-dependency logging infrastructure. Updates documentation with logging configuration, usage examples, and available logger categories.
1 parent 8a32927 commit b5b452c

22 files changed

+404
-205
lines changed

CLAUDE.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ HTTP Request → HttpRouter → RequestHandler → Browser (Puppeteer)
6767
| `lib/http-router.ts` | HTTP routing for UI, API, health, static files |
6868
| `lib/dithering.ts` | Image processing pipeline via GraphicsMagick |
6969
| `lib/scheduleStore.ts` | Schedule CRUD operations with JSON persistence |
70+
| `lib/logger.ts` | LogTape-based structured logging with module categories |
7071
| `const.ts` | All configuration constants, environment detection |
7172
| `error.ts` | Custom error classes for browser lifecycle management |
7273

@@ -133,10 +134,44 @@ bun run mock:server
133134
MOCK_HA=true bun run dev
134135
```
135136

137+
## Logging
138+
139+
Uses [LogTape](https://logtape.org) for structured, timestamped logging with zero dependencies.
140+
141+
### Log Format
142+
```
143+
[2025-12-30T11:19:53.454Z] [INFO ] [scheduler] Starting scheduler...
144+
[2025-12-30T11:19:53.454Z] [INFO ] [app] Server started at http://localhost:10000
145+
```
146+
147+
### Usage
148+
```typescript
149+
import { appLogger, browserLogger, schedulerLogger } from './lib/logger.js'
150+
151+
const log = appLogger()
152+
log.info`Server started at ${url}`
153+
log.debug`Processing ${count} items`
154+
log.error`Failed: ${error.message}`
155+
```
156+
157+
### Available Loggers
158+
- `appLogger()` - Main app lifecycle
159+
- `browserLogger()` - Puppeteer/browser operations
160+
- `screenshotLogger()` - Screenshot capture
161+
- `schedulerLogger()` - Cron scheduling
162+
- `cronLogger()` - Cron job management
163+
- `webhookLogger()` - Webhook delivery
164+
- `httpLogger()` - HTTP routing
165+
- `uiLogger()` - UI serving
166+
- `ditheringLogger()` - Image processing
167+
- `navigationLogger()` - Page navigation
168+
- `configLogger()` - Configuration loading
169+
136170
## Environment Variables
137171

138172
| Variable | Default | Purpose |
139173
|----------|---------|---------|
174+
| `LOG_LEVEL` | `info` | Log verbosity: trace, debug, info, warning, error, fatal |
140175
| `MOCK_HA` | `false` | Use mock HA server for testing |
141176
| `BROWSER_TIMEOUT` | `60000` | Idle timeout before browser cleanup (ms) |
142177
| `MAX_SCREENSHOTS_BEFORE_RESTART` | `100` | Proactive browser restart threshold |

README.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,6 @@ If running Home Assistant OS in Proxmox, set the VM host type to `host` for Chro
4242
- **Never expose port 10000** directly to the internet
4343
- Access tokens are stored securely in Home Assistant's add-on configuration
4444

45-
The add-on includes an AppArmor security profile that restricts file and network access to only what's required for operation.
46-
4745
## Documentation
4846

4947
| Topic | Description |

trmnl-ha/DOCS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ For best e-ink results:
186186
- Tokens are passed to the browser in memory, not written to disk
187187
- Use a dedicated token for this add-on (revoke it to disable access)
188188

189-
### AppArmor Profile
189+
### AppArmor Profile (TODO)
190190

191191
The add-on includes an AppArmor security profile that:
192192

trmnl-ha/README.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,6 @@ If running Home Assistant OS in Proxmox, set the VM host type to `host` for Chro
4242
- **Never expose port 10000** directly to the internet
4343
- Access tokens are stored securely in Home Assistant's add-on configuration
4444

45-
The add-on includes an AppArmor security profile that restricts file and network access to only what's required for operation.
46-
4745
## Documentation
4846

4947
| Topic | Description |

trmnl-ha/config.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,14 @@ options:
3434
access_token: ""
3535
home_assistant_url: "http://homeassistant:8123"
3636
keep_browser_open: false
37+
debug_logging: false
3738

3839
# Schema for options validation
3940
schema:
4041
access_token: str
4142
home_assistant_url: str?
4243
keep_browser_open: bool?
44+
debug_logging: bool?
4345

4446
# Exclude regeneratable data from HA backups
4547
backup_exclude:

trmnl-ha/ha-trmnl/bun.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

trmnl-ha/ha-trmnl/const.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ interface Options {
2121
access_token?: string
2222
chromium_executable?: string
2323
keep_browser_open?: boolean
24+
debug_logging?: boolean
2425
}
2526

2627
/**
@@ -108,9 +109,10 @@ export const chromiumExecutable: string = isAddOn
108109
export const keepBrowserOpen: boolean = options.keep_browser_open ?? false
109110

110111
/**
111-
* Enable debug logging
112+
* Enable debug logging (from HA add-on configuration)
113+
* When true, sets log level to 'debug' for verbose output
112114
*/
113-
export const debug: boolean = false
115+
export const debugLogging: boolean = options.debug_logging ?? false
114116

115117
// =============================================================================
116118
// SERVER CONFIGURATION
@@ -228,11 +230,6 @@ export const CONTENT_TYPES: ContentTypeMap = {
228230
// SCHEDULER CONFIGURATION
229231
// =============================================================================
230232

231-
/**
232-
* Scheduler log prefix for console output
233-
*/
234-
export const SCHEDULER_LOG_PREFIX: string = '[Scheduler]'
235-
236233
/**
237234
* Schedule reload interval in milliseconds
238235
*/

trmnl-ha/ha-trmnl/eslint.config.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ export default tseslint.config(
2424
},
2525
},
2626
rules: {
27+
// Allow tagged template literals (LogTape logging syntax: log.info`message`)
28+
'@typescript-eslint/no-unused-expressions': [
29+
'error',
30+
{ allowTaggedTemplates: true },
31+
],
2732
// Unused vars with underscore prefix are allowed
2833
'@typescript-eslint/no-unused-vars': [
2934
'error',

trmnl-ha/ha-trmnl/lib/browser/navigation-commands.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ import {
1515
} from '../../const.js'
1616
import { CannotOpenPageError } from '../../error.js'
1717
import type { NavigationResult } from '../../types/domain.js'
18+
import { navigationLogger } from '../logger.js'
19+
20+
const log = navigationLogger()
1821

1922
/** Auth storage for localStorage injection */
2023
export type AuthStorage = Record<string, string>
@@ -163,7 +166,7 @@ export class WaitForPageLoad {
163166
{ timeout: 10000, polling: 100 }
164167
)
165168
} catch (_err) {
166-
console.log('Timeout waiting for HA to finish loading')
169+
log.debug`Timeout waiting for HA to finish loading`
167170
}
168171
}
169172
}
@@ -215,9 +218,7 @@ export class WaitForPageStable {
215218
stableChecks++
216219
if (stableChecks >= requiredStableChecks) {
217220
const actualWait = Date.now() - start
218-
console.debug(
219-
`Page stable after ${actualWait}ms (${stableChecks} checks)`
220-
)
221+
log.debug`Page stable after ${actualWait}ms (${stableChecks} checks)`
221222
return actualWait
222223
}
223224
} else {
@@ -231,7 +232,7 @@ export class WaitForPageStable {
231232
}
232233

233234
const actualWait = Date.now() - start
234-
console.debug(`Page stability timeout after ${actualWait}ms`)
235+
log.debug`Page stability timeout after ${actualWait}ms`
235236
return actualWait
236237
}
237238
}

trmnl-ha/ha-trmnl/lib/browserFacade.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
*/
88

99
import { BrowserRecoveryFailedError } from '../error.js'
10+
import { browserLogger } from './logger.js'
11+
12+
const log = browserLogger()
1013

1114
/** Browser instance interface (from screenshot.ts) */
1215
export interface BrowserInstance {
@@ -97,14 +100,12 @@ export class BrowserFacade {
97100
let attempts = 0
98101
let lastError: Error | null = null
99102

100-
console.log('[Recovery] Starting browser recovery...')
103+
log.info`Starting browser recovery...`
101104

102105
try {
103106
while (attempts < BrowserFacade.MAX_RECOVERY_ATTEMPTS) {
104107
attempts++
105-
console.log(
106-
`[Recovery] Attempt ${attempts}/${BrowserFacade.MAX_RECOVERY_ATTEMPTS}`
107-
)
108+
log.info`Recovery attempt ${attempts}/${BrowserFacade.MAX_RECOVERY_ATTEMPTS}`
108109

109110
try {
110111
await this.#browser.cleanup().catch(() => {})
@@ -113,16 +114,13 @@ export class BrowserFacade {
113114

114115
if (!this.#browser.isConnected()) throw new Error('Not connected')
115116

116-
console.log(`[Recovery] Success after ${attempts} attempt(s)`)
117+
log.info`Recovery success after ${attempts} attempt(s)`
117118
this.#recoveries++
118119
this.#failures = 0
119120
return
120121
} catch (err) {
121122
lastError = err as Error
122-
console.error(
123-
`[Recovery] Attempt ${attempts} failed:`,
124-
lastError.message
125-
)
123+
log.error`Recovery attempt ${attempts} failed: ${lastError.message}`
126124
}
127125
}
128126
throw new BrowserRecoveryFailedError(

0 commit comments

Comments
 (0)