Skip to content

Commit 82da042

Browse files
committed
Add MySQL database support
Implements MySQL/MariaDB as a production database backend alongside SQL Server and SQLite. Uses go-sql-driver/mysql with FormatDSN for connection management, batched session configuration via multiStatements, and @PARAM to ? positional placeholder translation. Driver (internal/db/mysql.go): - Implement MySQLDriver with full Driver interface (Query, Ping, Reconnect, Close) - Build DSN using mysql.Config{}.FormatDSN() for safe credential handling - Translate @PARAM syntax to ? positional placeholders - Batch session SET statements into single round-trip (multiStatements=true) - Use ceiling division for lock_timeout_ms to seconds conversion - Configure connection pool with sensible defaults (5 open, 2 idle, 5min lifetime) - Support TLS via encrypt config field (true, false, disable, skip-verify) Config and Validation: - Add "mysql" to ValidDatabaseTypes map - Add MySQL-specific validation (host, port, user, password, database required) - Reject "snapshot" isolation level for MySQL (SQL Server-specific) - Make DefaultSessionConfig database-type-aware (MySQL defaults to read_committed) - Update error messages to include "mysql" in valid types list - Update OpenAPI spec description for database type field CI: - Add test-mysql job with MySQL 8.0 service container - Gate test-mysql on main test job (needs: [test]) - Validate MySQL example configs in CI with running MySQL instance Examples: - Reorganize examples/ into sqlite/ and mysql/ subdirectories - Add examples/mysql/01-basic.yaml demonstrating MySQL connection and queries - Update Makefile validate-examples to use examples/sqlite/ path Tests: - Add unit tests for DSN construction, query translation, isolation mapping, pool config - Add integration tests for queries, params, concurrency, unicode, reconnect - Add TestValidateDatabase_MySQL for field and isolation validation - Update TestValidDatabaseTypes, TestLoad_InvalidDatabaseType, TestValidateDatabase_InvalidType - Update TestDatabaseConfig_DefaultSessionConfig with per-type cases Documentation: - Add MySQL safety table, user setup, configuration options, session settings - Add MySQL query syntax examples and operational considerations - Update session settings table, pagination notes, troubleshooting section - Mark MySQL as complete in roadmap
1 parent 2a5dd4f commit 82da042

28 files changed

Lines changed: 1744 additions & 63 deletions

.github/workflows/ci.yml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,49 @@ jobs:
2929

3030
- name: Smoke test - validate config
3131
run: ./sql-proxy -validate -config testdata/config.ci.yaml
32+
33+
test-mysql:
34+
needs: [test]
35+
runs-on: ubuntu-latest
36+
services:
37+
mysql:
38+
image: mysql:8.0
39+
env:
40+
MYSQL_ROOT_PASSWORD: testpass
41+
MYSQL_DATABASE: testdb
42+
ports:
43+
- 3306:3306
44+
options: >-
45+
--health-cmd="mysqladmin ping -h 127.0.0.1"
46+
--health-interval=10s
47+
--health-timeout=5s
48+
--health-retries=5
49+
steps:
50+
- name: Checkout
51+
uses: actions/checkout@v4
52+
53+
- name: Set up Go
54+
uses: actions/setup-go@v5
55+
with:
56+
go-version: '1.25'
57+
58+
- name: Run MySQL integration tests
59+
env:
60+
MYSQL_HOST: 127.0.0.1
61+
MYSQL_PORT: 3306
62+
MYSQL_USER: root
63+
MYSQL_PASSWORD: testpass
64+
MYSQL_DATABASE: testdb
65+
run: go test -v -run "MySQL" ./internal/db/
66+
67+
- name: Build
68+
run: go build -o sql-proxy .
69+
70+
- name: Validate MySQL example configs
71+
env:
72+
MYSQL_PASSWORD: testpass
73+
run: |
74+
for f in examples/mysql/*.yaml; do
75+
echo "=== $f ==="
76+
./sql-proxy -validate -config "$f" || exit 1
77+
done

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -316,10 +316,10 @@ test-clean:
316316
validate: build
317317
./$(BINARY_NAME) -validate -config config.yaml
318318

319-
# Validate all example configs
319+
# Validate all example configs (sqlite examples run without external dependencies)
320320
validate-examples: build
321321
@echo "Validating example configs..."
322-
@for f in examples/*.yaml; do \
322+
@for f in examples/sqlite/*.yaml; do \
323323
echo "=== $$f ===" && \
324324
./$(BINARY_NAME) -validate -config "$$f" || exit 1; \
325325
done

README.md

Lines changed: 126 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
# SQL Proxy Service
22

3-
A lightweight, production-grade Go service that exposes predefined SQL queries as HTTP endpoints. Supports **SQL Server** and **SQLite** databases. Runs as a system service on **Windows**, **Linux**, and **macOS** with **zero impact on the source database** and **zero maintenance** requirements.
3+
A lightweight, production-grade Go service that exposes predefined SQL queries as HTTP endpoints. Supports **SQL Server**, **MySQL**, and **SQLite** databases. Runs as a system service on **Windows**, **Linux**, and **macOS** with **zero impact on the source database** and **zero maintenance** requirements.
44

55
## Features
66

7-
- **Multi-Database Support** - SQL Server and SQLite (same query syntax)
7+
- **Multi-Database Support** - SQL Server, MySQL, and SQLite (same query syntax)
88
- **Cross-Platform Service** - Windows Service, Linux systemd, macOS launchd
99
- **YAML Configuration** - Easy query management, no code changes
1010
- **Read-only Safety** - Zero interference with production database
@@ -50,6 +50,19 @@ This service is designed to safely read from production databases without interf
5050
| Session | `IMPLICIT_TRANSACTIONS OFF` | No accidental open transactions |
5151
| Database | Read-only SQL user | Database enforces no writes possible |
5252

53+
### MySQL Safety
54+
55+
| Level | Setting | Purpose |
56+
|-------|---------|---------|
57+
| Connection | `parseTime=true` | Parses DATE/DATETIME as Go time.Time |
58+
| Connection | `collation=utf8mb4_general_ci` | Full Unicode support (utf8mb4 charset) |
59+
| Connection | `interpolateParams=false` | Prevents client-side parameter interpolation |
60+
| Connection | Max 5 connections, 5min lifetime | Conservative pool footprint |
61+
| Session | `innodb_lock_wait_timeout` | Fails fast (5s) if lock needed |
62+
| Session | `TRANSACTION READ ONLY` | Prevents writes when readonly is true |
63+
| Session | Configurable isolation level | Default: READ COMMITTED |
64+
| Database | Read-only MySQL user | Database enforces no writes possible |
65+
5366
### SQLite Safety
5467

5568
| Level | Setting | Purpose |
@@ -87,6 +100,21 @@ DENY CREATE PROCEDURE TO sqlproxy_reader;
87100
DENY CREATE FUNCTION TO sqlproxy_reader;
88101
```
89102

103+
## MySQL User Setup (REQUIRED)
104+
105+
Create a dedicated read-only user in MySQL. Connect as admin and run:
106+
107+
```sql
108+
-- Create a user with read-only access
109+
CREATE USER 'sqlproxy_reader'@'%' IDENTIFIED BY 'YourSecurePassword123!';
110+
111+
-- Grant ONLY read access to your database
112+
GRANT SELECT ON YourDatabaseName.* TO 'sqlproxy_reader'@'%';
113+
114+
-- Apply changes
115+
FLUSH PRIVILEGES;
116+
```
117+
90118
## Building
91119

92120
```bash
@@ -292,7 +320,7 @@ server:
292320

293321
databases:
294322
- name: "primary"
295-
type: "sqlserver" # sqlserver or sqlite (required)
323+
type: "sqlserver" # sqlserver, mysql, or sqlite (required)
296324
host: "your-server.rds.amazonaws.com"
297325
port: 1433
298326
user: "sqlproxy_reader"
@@ -415,6 +443,69 @@ workflows:
415443
template: '{"success": true}'
416444
```
417445
446+
### MySQL Support
447+
448+
MySQL databases are supported as a production backend. Use `type: "mysql"` with standard host/port/user/password connection settings:
449+
450+
```yaml
451+
databases:
452+
- name: "primary"
453+
type: "mysql"
454+
host: "your-mysql-server.example.com"
455+
port: 3306
456+
user: "sqlproxy_reader"
457+
password: "${DB_PASSWORD}"
458+
database: "YourDB"
459+
readonly: true # Defaults to true if omitted
460+
encrypt: "true" # TLS: true, false, disable, skip-verify
461+
```
462+
463+
#### MySQL Configuration Options
464+
465+
| Setting | Default | Description |
466+
|---------|---------|-------------|
467+
| `host` | (required) | MySQL server hostname |
468+
| `port` | `3306` | MySQL server port |
469+
| `user` | (required) | MySQL username |
470+
| `password` | (required) | MySQL password (supports `${ENV_VAR}` syntax) |
471+
| `database` | (required) | Database name |
472+
| `readonly` | `true` | Sets session to READ ONLY transaction mode |
473+
| `encrypt` | `false` | TLS mode: `true`, `false`, `disable`, `skip-verify` |
474+
475+
#### MySQL Session Settings
476+
477+
| Setting | Default (readonly) | Default (readwrite) | Description |
478+
|---------|-------------------|---------------------|-------------|
479+
| `isolation` | `read_committed` | `read_committed` | Transaction isolation level |
480+
| `lock_timeout_ms` | `5000` | `5000` | InnoDB lock wait timeout (converted to seconds) |
481+
482+
#### MySQL Query Syntax
483+
484+
All queries use `@param` syntax (the driver translates to `?` positional placeholders for MySQL):
485+
486+
```sql
487+
-- Named parameters are translated automatically
488+
SELECT * FROM users WHERE status = @status AND age > @age
489+
-- Becomes: SELECT * FROM users WHERE status = ? AND age > ?
490+
491+
-- Use LIMIT for pagination (MySQL style)
492+
SELECT * FROM items ORDER BY id LIMIT @limit OFFSET @offset
493+
494+
-- Optional filter pattern (same as other databases)
495+
SELECT * FROM users WHERE (@status IS NULL OR status = @status)
496+
```
497+
498+
#### MySQL Operational Considerations
499+
500+
**When to use MySQL:**
501+
- Production deployments with MySQL/MariaDB infrastructure
502+
- Read-heavy workloads with connection pooling
503+
- Applications already running on MySQL/Aurora
504+
505+
**When NOT to use MySQL:**
506+
- If you need SQL Server-specific features (AG routing, SNAPSHOT isolation)
507+
- If you need embedded/serverless (use SQLite instead)
508+
418509
### SQLite Support
419510

420511
SQLite databases are supported for testing and lightweight deployments. Use `type: "sqlite"` with a `path` instead of host/port/user/password:
@@ -527,13 +618,13 @@ The driver automatically configures SQLite for optimal concurrent performance:
527618
| In-memory | `:memory:` | Lost on restart | Testing |
528619

529620
**Query Syntax:**
530-
- All queries use `@param` syntax (driver translates to `$param` for SQLite)
621+
- All queries use `@param` syntax (driver translates to `$param` for SQLite, `?` for MySQL)
531622
- Use `LIMIT` instead of `TOP` for pagination:
532623
```sql
533624
-- SQL Server style (works on SQL Server)
534625
SELECT TOP (@limit) * FROM items
535626
536-
-- SQLite style (works on SQLite)
627+
-- MySQL/SQLite style (works on MySQL and SQLite)
537628
SELECT * FROM items LIMIT @limit
538629
```
539630

@@ -543,11 +634,11 @@ Session settings control database behavior at query execution time. Settings can
543634

544635
**Database-Specific Behavior:**
545636

546-
| Setting | SQL Server | SQLite |
547-
|---------|------------|--------|
548-
| `isolation` | Sets transaction isolation level | Ignored (SQLite has limited isolation) |
549-
| `lock_timeout_ms` | `SET LOCK_TIMEOUT` | Maps to `busy_timeout` pragma |
550-
| `deadlock_priority` | `SET DEADLOCK_PRIORITY` | Ignored (SQLite handles differently) |
637+
| Setting | SQL Server | MySQL | SQLite |
638+
|---------|------------|-------|--------|
639+
| `isolation` | Sets transaction isolation level | Sets transaction isolation level | Ignored (SQLite has limited isolation) |
640+
| `lock_timeout_ms` | `SET LOCK_TIMEOUT` | `innodb_lock_wait_timeout` (seconds) | Maps to `busy_timeout` pragma |
641+
| `deadlock_priority` | `SET DEADLOCK_PRIORITY` | Ignored (InnoDB handles internally) | Ignored (SQLite handles differently) |
551642

552643
**SQL Server Implicit Defaults (based on `readonly` flag):**
553644

@@ -631,7 +722,7 @@ curl "http://localhost:8081/api/checkins?_timeout=120"
631722

632723
Pagination is handled at the query level using database-native syntax. This is more efficient than service-level truncation because the database stops scanning once the limit is reached.
633724

634-
> **Note:** SQL Server uses `TOP`, SQLite uses `LIMIT`. Write queries for your specific database type.
725+
> **Note:** SQL Server uses `TOP`, MySQL and SQLite use `LIMIT`. Write queries for your specific database type.
635726

636727
#### Simple Limit (SQL Server: TOP, SQLite: LIMIT)
637728

@@ -2317,6 +2408,16 @@ Status values:
23172408
}
23182409
```
23192410

2411+
MySQL example:
2412+
```json
2413+
{
2414+
"database": "analytics",
2415+
"status": "connected",
2416+
"type": "mysql",
2417+
"readonly": true
2418+
}
2419+
```
2420+
23202421
Returns 404 only if the database name doesn't exist in configuration.
23212422

23222423
### OpenAPI / Swagger
@@ -2449,6 +2550,12 @@ your-domain.com {
24492550
8. **Low deadlock priority** - Always yields to production app
24502551
9. **ApplicationIntent=ReadOnly** - Enables AG read routing
24512552
2553+
**MySQL Specific:**
2554+
5. **Read-only MySQL user** - `SELECT` privilege only, no write grants
2555+
6. **TLS encryption** - `encrypt: "true"` for production connections
2556+
7. **Session READ ONLY** - `readonly: true` sets transaction to read-only mode
2557+
8. **Lock timeout (5s)** - Fails fast via `innodb_lock_wait_timeout`
2558+
24522559
**SQLite Specific:**
24532560
5. **Read-only mode** - `readonly: true` opens DB in read-only mode
24542561
6. **File permissions** - Ensure appropriate filesystem permissions
@@ -2482,6 +2589,13 @@ your-domain.com {
24822589
- Verify security group allows port 1433
24832590
- Check credentials in config
24842591
2592+
### Database connection issues (MySQL)
2593+
- Check `/_/health` endpoint for status
2594+
- Verify security group/firewall allows port 3306
2595+
- Check credentials and database name in config
2596+
- Verify TLS settings match server configuration (`encrypt: "true"` or `"skip-verify"`)
2597+
- Check `max_connections` on MySQL server if getting connection pool exhaustion
2598+
24852599
### Database issues (SQLite)
24862600
24872601
**"database is locked" errors:**
@@ -2710,7 +2824,7 @@ All unit and integration tests use SQLite in-memory databases (`:memory:`) to av
27102824

27112825
Planned features for future releases:
27122826

2713-
- [ ] **MySQL Support** - Add MySQL/MariaDB as a database backend option alongside SQL Server and SQLite.
2827+
- [x] **MySQL Support** - Add MySQL/MariaDB as a database backend option alongside SQL Server and SQLite.
27142828
- [ ] **PostgreSQL Support** - Add PostgreSQL as a database backend option.
27152829
- [ ] **TLS Support** - Native HTTPS termination without requiring a reverse proxy (Caddy/nginx). Will support configurable certificate paths and automatic Let's Encrypt integration.
27162830
- [x] **Rate Limiting** - Per-endpoint and per-client rate limiting to protect database resources from excessive requests.

TESTS.md

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,17 @@ Run `make test-cover` for current coverage statistics.
2222
- **TestLoad_InvalidTimeout**: TestLoad_InvalidTimeout checks timeout validation: positive values, max >= default
2323
- **TestLoad_NoDatabases**: TestLoad_NoDatabases ensures at least one database connection is required
2424
- **TestLoad_DuplicateDatabaseNames**: TestLoad_DuplicateDatabaseNames ensures database names must be unique across connections
25-
- **TestLoad_InvalidDatabaseType**: TestLoad_InvalidDatabaseType rejects unsupported database types like mysql
25+
- **TestLoad_InvalidDatabaseType**: TestLoad_InvalidDatabaseType rejects unsupported database types
2626
- **TestLoad_SQLiteMissingPath**: TestLoad_SQLiteMissingPath ensures SQLite databases require a path field
2727
- **TestLoad_SQLServerMissingFields**: TestLoad_SQLServerMissingFields validates SQL Server requires host, port, user, password, database
2828
- **TestLoad_InvalidLogLevel**: TestLoad_InvalidLogLevel rejects log levels other than debug/info/warn/error
2929
- **TestLoad_InvalidIsolationLevel**: TestLoad_InvalidIsolationLevel rejects invalid SQL Server isolation level names
3030
- **TestDatabaseConfig_IsReadOnly**: TestDatabaseConfig_IsReadOnly verifies readonly defaults to true when nil
31-
- **TestDatabaseConfig_DefaultSessionConfig**: TestDatabaseConfig_DefaultSessionConfig checks implicit defaults based on readonly flag
31+
- **TestDatabaseConfig_DefaultSessionConfig**: TestDatabaseConfig_DefaultSessionConfig checks implicit defaults based on type and readonly flag
3232
- **TestValidIsolationLevels**: TestValidIsolationLevels checks the ValidIsolationLevels map contains correct entries
3333
- **TestValidDeadlockPriorities**: TestValidDeadlockPriorities checks the ValidDeadlockPriorities map for low/normal/high
3434
- **TestValidJournalModes**: TestValidJournalModes checks ValidJournalModes for SQLite: wal/delete/truncate/memory/off
35-
- **TestValidDatabaseTypes**: TestValidDatabaseTypes checks ValidDatabaseTypes contains sqlserver and sqlite only
35+
- **TestValidDatabaseTypes**: TestValidDatabaseTypes checks ValidDatabaseTypes contains expected database types
3636
- **TestLoad_VariablesSection**: TestLoad_VariablesSection verifies the variables section with values
3737
- **TestLoad_VariablesDefaultValues**: TestLoad_VariablesDefaultValues verifies ${VAR:default} syntax works correctly
3838
- **TestLoad_VariablesEnvFileSupport**: TestLoad_VariablesEnvFileSupport verifies loading variables from env file
@@ -74,7 +74,7 @@ Run `make test-cover` for current coverage statistics.
7474
- **TestNewDriver_SQLite**: TestNewDriver_SQLite verifies factory creates SQLite driver with :memory: path
7575
- **TestNewDriver_SQLiteExplicit**: TestNewDriver_SQLiteExplicit confirms returned driver is *SQLiteDriver type
7676
- **TestNewDriver_EmptyTypeReturnsError**: TestNewDriver_EmptyTypeReturnsError ensures empty type is rejected
77-
- **TestNewDriver_MySQL_NotImplemented**: TestNewDriver_MySQL_NotImplemented confirms mysql type returns not-implemented error
77+
- **TestNewDriver_MySQL**: TestNewDriver_MySQL verifies factory creates MySQL driver (requires running MySQL)
7878
- **TestNewDriver_Postgres_NotImplemented**: TestNewDriver_Postgres_NotImplemented confirms postgres type returns not-implemented error
7979
- **TestNewDriver_UnknownType**: TestNewDriver_UnknownType rejects unrecognized database types like oracle
8080
- **TestNewDriver_SQLiteInvalidPath**: TestNewDriver_SQLiteInvalidPath ensures SQLite driver requires non-empty path
@@ -100,6 +100,33 @@ Run `make test-cover` for current coverage statistics.
100100
- **TestManager_ConcurrentReconnectAll**: TestManager_ConcurrentReconnectAll tests concurrent ReconnectAll calls
101101
- **TestManager_MixedDatabaseTypes**: TestManager_MixedDatabaseTypes manages SQLite connections with different readonly/settings
102102

103+
### mysql_test.go
104+
105+
- **TestBuildMySQLDSN_Default**: TestBuildMySQLDSN_Default verifies DSN construction with default port and settings
106+
- **TestBuildMySQLDSN_CustomPort**: TestBuildMySQLDSN_CustomPort verifies custom port is used in DSN
107+
- **TestBuildMySQLDSN_TLSOptions**: TestBuildMySQLDSN_TLSOptions tests all TLS/encrypt configuration variants
108+
- **TestBuildMySQLDSN_SpecialCharsInPassword**: TestBuildMySQLDSN_SpecialCharsInPassword verifies password with special chars is included as-is
109+
- **TestMySQLDriver_TranslateQuery**: TestMySQLDriver_TranslateQuery tests @param to ? positional placeholder translation
110+
- **TestMySQLDriver_TranslateQuery_Values**: TestMySQLDriver_TranslateQuery_Values verifies parameter values are correctly ordered
111+
- **TestMySQLDriver_TranslateQuery_NilValue**: TestMySQLDriver_TranslateQuery_NilValue verifies nil values are passed through
112+
- **TestMySQLIsolationToSQL**: TestMySQLIsolationToSQL tests conversion of config isolation strings to MySQL syntax
113+
- **TestMySQLDriver_ConfigurePool**: TestMySQLDriver_ConfigurePool verifies connection pool settings are applied
114+
- **TestMySQLDriver_ConfigValidation**: TestMySQLDriver_ConfigValidation tests that invalid configs produce errors
115+
- **TestNewMySQLDriver_Integration**: TestNewMySQLDriver_Integration verifies driver creation against a real MySQL instance
116+
- **TestNewMySQLDriver_ReadWrite**: TestNewMySQLDriver_ReadWrite confirms explicit readonly=false enables write mode
117+
- **TestMySQLDriver_Ping**: TestMySQLDriver_Ping confirms Ping returns nil for healthy connection
118+
- **TestMySQLDriver_Reconnect**: TestMySQLDriver_Reconnect tests connection re-establishment after close
119+
- **TestMySQLDriver_Config**: TestMySQLDriver_Config verifies Config() returns original configuration
120+
- **TestMySQLDriver_Query_Simple**: TestMySQLDriver_Query_Simple executes basic SELECT and validates returned columns
121+
- **TestMySQLDriver_Query_WithParams**: TestMySQLDriver_Query_WithParams verifies @param named parameters work correctly
122+
- **TestMySQLDriver_Query_NullParams**: TestMySQLDriver_Query_NullParams tests NULL parameter handling for optional filters
123+
- **TestMySQLDriver_Query_EmptyResult**: TestMySQLDriver_Query_EmptyResult confirms empty result set returns zero-length slice
124+
- **TestMySQLDriver_Query_Timeout**: TestMySQLDriver_Query_Timeout verifies context deadline expiration stops query
125+
- **TestMySQLDriver_Query_SpecialCharacters**: TestMySQLDriver_Query_SpecialCharacters ensures SQL injection strings are safely escaped
126+
- **TestMySQLDriver_Query_Unicode**: TestMySQLDriver_Query_Unicode validates CJK, Cyrillic, Arabic, and emoji preservation
127+
- **TestMySQLDriver_WriteOperations_RowsAffected**: TestMySQLDriver_WriteOperations_RowsAffected tests that write operations return correct rows affected
128+
- **TestMySQLDriver_Query_Concurrent**: TestMySQLDriver_Query_Concurrent runs parallel queries against MySQL
129+
103130
### sqlite_test.go
104131

105132
- **TestNewSQLiteDriver_InMemory**: TestNewSQLiteDriver_InMemory verifies in-memory SQLite driver creation with :memory: path
@@ -148,6 +175,7 @@ Run `make test-cover` for current coverage statistics.
148175
- **TestValidateDatabase_InvalidType**: TestValidateDatabase_InvalidType ensures unsupported database types are rejected
149176
- **TestValidateDatabase_SQLite**: TestValidateDatabase_SQLite tests SQLite-specific validation: path, journal mode, timeout
150177
- **TestValidateDatabase_SQLServer**: TestValidateDatabase_SQLServer tests SQL Server validation: host, port, isolation, timeout
178+
- **TestValidateDatabase_MySQL**: TestValidateDatabase_MySQL tests MySQL-specific validation: host, port, user, password, database, isolation
151179
- **TestValidateDatabase_EnvVarWarning**: TestValidateDatabase_EnvVarWarning tests unresolved env vars generate warnings
152180
- **TestValidateLogging**: TestValidateLogging tests log level and rotation settings validation
153181
- **TestValidateDebug**: TestValidateDebug tests debug config validation rules

0 commit comments

Comments
 (0)