first commit
This commit is contained in:
27
README.md
Normal file
27
README.md
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# StatusServer
|
||||||
|
|
||||||
|
A lightweight **pub/sub server** built on WebSockets.
|
||||||
|
It is designed to broadcast **status updates** and **error messages** to connected clients in real-time.
|
||||||
|
No database, no persistence — just simple and fast messaging.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Features
|
||||||
|
- **Real-time updates** over WebSockets
|
||||||
|
- **Pub/Sub model**: subscribe to topics and receive broadcasts
|
||||||
|
- **Zero persistence**: messages are ephemeral and only delivered to connected clients
|
||||||
|
- **Lightweight**: no external dependencies beyond WebSockets
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🗂️ Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
Client <--> StatusServer
|
||||||
|
^ |
|
||||||
|
| |-- publish(info/status/warning/error)
|
||||||
|
|<-- broadcasted messages
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Publish**: send a message (info, status, warning or error) to the server.
|
||||||
|
- **Broadcast**: server pushes it to all connected subscribers.
|
45
go.mod
Normal file
45
go.mod
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
module statusServer
|
||||||
|
|
||||||
|
go 1.24.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
gitea.tecamino.com/paadi/pubSub v1.0.1
|
||||||
|
gitea.tecamino.com/paadi/tecamino-dbm v0.1.1
|
||||||
|
gitea.tecamino.com/paadi/tecamino-json_data v0.1.0
|
||||||
|
gitea.tecamino.com/paadi/tecamino-logger v0.2.1
|
||||||
|
github.com/gin-contrib/cors v1.7.6
|
||||||
|
github.com/gin-gonic/gin v1.10.1
|
||||||
|
github.com/gorilla/websocket v1.5.3
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/bytedance/sonic v1.13.3 // indirect
|
||||||
|
github.com/bytedance/sonic/loader v0.2.4 // indirect
|
||||||
|
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
|
||||||
|
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||||
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
|
github.com/go-playground/validator/v10 v10.26.0 // indirect
|
||||||
|
github.com/goccy/go-json v0.10.5 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||||
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
|
github.com/ugorji/go/codec v1.3.0 // indirect
|
||||||
|
go.uber.org/multierr v1.10.0 // indirect
|
||||||
|
go.uber.org/zap v1.27.0 // indirect
|
||||||
|
golang.org/x/arch v0.18.0 // indirect
|
||||||
|
golang.org/x/crypto v0.39.0 // indirect
|
||||||
|
golang.org/x/net v0.41.0 // indirect
|
||||||
|
golang.org/x/sys v0.33.0 // indirect
|
||||||
|
golang.org/x/text v0.26.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.36.6 // indirect
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
111
go.sum
Normal file
111
go.sum
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
gitea.tecamino.com/paadi/pubSub v1.0.1 h1:Of91JAVaiaqXhzFjm+jwSJipPbST+A1WQz1DEEhLQm4=
|
||||||
|
gitea.tecamino.com/paadi/pubSub v1.0.1/go.mod h1:SBPTSD/JQWRbwqsSNoSPhV81IDTreP0TMyvLhmK3P2M=
|
||||||
|
gitea.tecamino.com/paadi/tecamino-dbm v0.1.1 h1:vAq7mwUxlxJuLzCQSDMrZCwo8ky5usWi9Qz+UP+WnkI=
|
||||||
|
gitea.tecamino.com/paadi/tecamino-dbm v0.1.1/go.mod h1:+tmf1rjPaKEoNeUcr1vdtoFIFweNG3aUGevDAl3NMBk=
|
||||||
|
gitea.tecamino.com/paadi/tecamino-json_data v0.1.0 h1:zp3L8qUvkVqzuesQdMh/SgZZZbX3pGD9NYa6jtz+JvA=
|
||||||
|
gitea.tecamino.com/paadi/tecamino-json_data v0.1.0/go.mod h1:/FKhbVYuhiNlQp4552rJJQIhynjLarDzfrgXpupkwZU=
|
||||||
|
gitea.tecamino.com/paadi/tecamino-logger v0.2.1 h1:sQTBKYPdzn9mmWX2JXZBtGBvNQH7cuXIwsl4TD0aMgE=
|
||||||
|
gitea.tecamino.com/paadi/tecamino-logger v0.2.1/go.mod h1:FkzRTldUBBOd/iy2upycArDftSZ5trbsX5Ira5OzJgM=
|
||||||
|
github.com/bytedance/sonic v1.13.3 h1:MS8gmaH16Gtirygw7jV91pDCN33NyMrPbN7qiYhEsF0=
|
||||||
|
github.com/bytedance/sonic v1.13.3/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
|
||||||
|
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||||
|
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
|
||||||
|
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||||
|
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
||||||
|
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||||
|
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
|
||||||
|
github.com/gin-contrib/cors v1.7.6 h1:3gQ8GMzs1Ylpf70y8bMw4fVpycXIeX1ZemuSQIsnQQY=
|
||||||
|
github.com/gin-contrib/cors v1.7.6/go.mod h1:Ulcl+xN4jel9t1Ry8vqph23a60FwH9xVLd+3ykmTjOk=
|
||||||
|
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||||
|
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||||
|
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
||||||
|
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
|
github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
|
||||||
|
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||||
|
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||||
|
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||||
|
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
|
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||||
|
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||||
|
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||||
|
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
|
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
|
||||||
|
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||||
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
|
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
|
||||||
|
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
|
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||||
|
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||||
|
golang.org/x/arch v0.18.0 h1:WN9poc33zL4AzGxqf8VtpKUnGvMi8O9lhNyBMF/85qc=
|
||||||
|
golang.org/x/arch v0.18.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
||||||
|
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||||
|
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||||
|
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||||
|
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||||
|
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
|
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||||
|
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||||
|
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||||
|
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
125
handlers/clientHandler.go
Normal file
125
handlers/clientHandler.go
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"statusServer/models"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
json_dataModels "gitea.tecamino.com/paadi/tecamino-json_data/models"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ClientHandler manages active websocket clients.
|
||||||
|
// - Keeps a map of connected clients (thread-safe).
|
||||||
|
// - Provides helper methods to connect, lookup, and send responses.
|
||||||
|
type ClientHandler struct {
|
||||||
|
sync.RWMutex // protects access to Clients map
|
||||||
|
clients models.Clients // map[string]*Client (id → client)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendBroadcast sends a raw message to all clients in the global Broadcast list.
|
||||||
|
// Note: uses `models.Broadcast` (separate global client slice).
|
||||||
|
func SendBroadcast(data []byte) {
|
||||||
|
for _, c := range models.Broadcast {
|
||||||
|
c.SendData(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConnectionHandler creates and initializes a new ClientHandler
|
||||||
|
// with an empty client map.
|
||||||
|
func NewConnectionHandler() *ClientHandler {
|
||||||
|
return &ClientHandler{
|
||||||
|
clients: models.NewClients(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConnectNewClient registers a new websocket client by ID.
|
||||||
|
// - If client with same ID already exists, returns the existing client.
|
||||||
|
// - Otherwise, creates a new connection and stores it in the handler map.
|
||||||
|
func (cH *ClientHandler) ConnectNewClient(id string, c *gin.Context) (client *models.Client, err error) {
|
||||||
|
// Check if client already exists (reuse connection).
|
||||||
|
if _, exists := cH.clients[id]; exists {
|
||||||
|
return cH.clients[id], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new websocket client instance.
|
||||||
|
client, err = models.ConnectNewClient(id, c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure cleanup: when client disconnects, remove from map.
|
||||||
|
client.OnClose = func(code int, reason string) {
|
||||||
|
delete(cH.clients, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add client to handler map.
|
||||||
|
cH.Lock()
|
||||||
|
cH.clients[id] = client
|
||||||
|
cH.Unlock()
|
||||||
|
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetClient retrieves a client by ID.
|
||||||
|
// Returns nil if client does not exist.
|
||||||
|
func (c *ClientHandler) GetClient(id string) *models.Client {
|
||||||
|
if client, ok := c.clients[id]; ok {
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cH *ClientHandler) RemoveClient(id string) {
|
||||||
|
delete(cH.clients, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendResponse sends a structured JSON response to a given client by ID.
|
||||||
|
// - If client is not found, returns error.
|
||||||
|
// - Otherwise marshals the Response and sends via websocket.
|
||||||
|
func (c *ClientHandler) SendResponse(id string, r *json_dataModels.Response) error {
|
||||||
|
client, ok := c.clients[id]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("client not found for id %s", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode response to JSON.
|
||||||
|
b, err := json.Marshal(*r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send JSON payload over websocket.
|
||||||
|
client.SendData(b)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Sending helpers (proxy to server messaging system) ---
|
||||||
|
|
||||||
|
// SendInfo sends an "info" log message to the server.
|
||||||
|
func (c *ClientHandler) SendInfo(id string, data any) error {
|
||||||
|
return c.clients[id].SendInfo(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendInfo sends an "info" log message to the server.
|
||||||
|
func (c *ClientHandler) SendStatus(id string, data any) error {
|
||||||
|
return c.clients[id].SendStatus(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendDebug sends a "debug" log message to the server.
|
||||||
|
func (c *ClientHandler) SendDebug(id string, data any) error {
|
||||||
|
return c.clients[id].SendDebug(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendWarning sends a "warning" log message to the server.
|
||||||
|
func (c *ClientHandler) SendWarning(id string, data any) error {
|
||||||
|
return c.clients[id].SendWarning(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendError sends an "error" log message to the server.
|
||||||
|
func (c *ClientHandler) SendError(id string, data any) error {
|
||||||
|
return c.clients[id].SendError(data)
|
||||||
|
}
|
354
models/client.go
Normal file
354
models/client.go
Normal file
@@ -0,0 +1,354 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"slices"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
pubSubModels "gitea.tecamino.com/paadi/pubSub/models"
|
||||||
|
"gitea.tecamino.com/paadi/tecamino-logger/logging"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Origins []string = []string{"*"}
|
||||||
|
|
||||||
|
var Broadcast Clients = make(Clients)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Time allowed to write a message to the peer.
|
||||||
|
writeWait = 10 * time.Second
|
||||||
|
|
||||||
|
// Time allowed to read the next pong message from the peer.
|
||||||
|
pongWait = 30 * time.Second
|
||||||
|
|
||||||
|
// Send pings to peer with this period. Must be less than pongWait.
|
||||||
|
pingPeriod = (pongWait * 9) / 10
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
id string
|
||||||
|
connected bool
|
||||||
|
conn *websocket.Conn `json:"-"`
|
||||||
|
OnOpen func()
|
||||||
|
OnMessage func(data []byte)
|
||||||
|
OnClose func(code int, reason string)
|
||||||
|
OnError func(err error)
|
||||||
|
OnWarning func(warn string)
|
||||||
|
OnPing func()
|
||||||
|
OnPong func()
|
||||||
|
send chan []byte
|
||||||
|
unregister chan []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
var upgrader = websocket.Upgrader{
|
||||||
|
CheckOrigin: func(r *http.Request) bool {
|
||||||
|
if len(Origins) == 0 {
|
||||||
|
return false
|
||||||
|
} else if Origins[0] == "*" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return slices.Contains(Origins, r.Header.Get("Origin"))
|
||||||
|
},
|
||||||
|
EnableCompression: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClient(ip, id string, port uint, logger *logging.Logger) (*Client, error) {
|
||||||
|
u := url.URL{Scheme: "ws", Host: fmt.Sprintf("%s:%d", ip, port), Path: "status", RawQuery: "id=" + id}
|
||||||
|
|
||||||
|
c := &Client{
|
||||||
|
id: id,
|
||||||
|
connected: true,
|
||||||
|
send: make(chan []byte, 512),
|
||||||
|
}
|
||||||
|
|
||||||
|
dialer := websocket.DefaultDialer
|
||||||
|
conn, resp, err := dialer.Dial(u.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
if c.OnError != nil {
|
||||||
|
c.OnError(err)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("dial error %v (status %v)", err, resp)
|
||||||
|
}
|
||||||
|
c.conn = conn
|
||||||
|
|
||||||
|
//Setup control handlers
|
||||||
|
logger.Debug("NewClient", "set PingHandler")
|
||||||
|
conn.SetPingHandler(func(appData string) error {
|
||||||
|
if c.OnPing != nil {
|
||||||
|
c.OnPing()
|
||||||
|
}
|
||||||
|
conn.SetReadDeadline(time.Now().Add(pongWait))
|
||||||
|
conn.SetWriteDeadline(time.Now().Add(pongWait))
|
||||||
|
if err := conn.WriteControl(websocket.PongMessage, []byte(appData), time.Now().Add(2*pongWait)); err != nil {
|
||||||
|
c.OnError(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
logger.Debug("NewClient", "set PongHandler")
|
||||||
|
conn.SetPongHandler(func(appData string) error {
|
||||||
|
conn.SetReadDeadline(time.Now().Add(pongWait))
|
||||||
|
if c.OnPong != nil {
|
||||||
|
c.OnPong()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
// Start reading messages from client
|
||||||
|
logger.Debug("NewClient", "start read goroutine")
|
||||||
|
go c.Read()
|
||||||
|
logger.Debug("NewClient", "start write goroutine")
|
||||||
|
|
||||||
|
go c.Write()
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConnectNewClient(id string, c *gin.Context) (*Client, error) {
|
||||||
|
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("websocket upgrade error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client := &Client{
|
||||||
|
id: id,
|
||||||
|
connected: true,
|
||||||
|
conn: conn,
|
||||||
|
send: make(chan []byte, 512),
|
||||||
|
unregister: make(chan []byte, 256),
|
||||||
|
}
|
||||||
|
|
||||||
|
Broadcast[client.id] = client
|
||||||
|
|
||||||
|
conn.SetReadDeadline(time.Now().Add(pongWait))
|
||||||
|
|
||||||
|
conn.SetPingHandler(func(appData string) error {
|
||||||
|
if client.OnPing != nil {
|
||||||
|
client.OnPing()
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.SetReadDeadline(time.Now().Add(pongWait))
|
||||||
|
conn.SetWriteDeadline(time.Now().Add(writeWait))
|
||||||
|
if err := client.conn.WriteControl(websocket.PongMessage, []byte(appData), time.Now().Add(pongWait)); err != nil {
|
||||||
|
client.OnError(err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
conn.SetPongHandler(func(appData string) error {
|
||||||
|
conn.SetReadDeadline(time.Now().Add(pongWait))
|
||||||
|
if client.OnPong != nil {
|
||||||
|
client.OnPong()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
// Start reading messages from client
|
||||||
|
go client.Read()
|
||||||
|
go client.Write()
|
||||||
|
go client.PingInterval(pingPeriod)
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Read() {
|
||||||
|
if c.OnOpen != nil {
|
||||||
|
c.OnOpen()
|
||||||
|
}
|
||||||
|
|
||||||
|
c.conn.SetReadDeadline(time.Now().Add(writeWait))
|
||||||
|
for c.connected {
|
||||||
|
msgType, msg, err := c.conn.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
c.handleError(fmt.Errorf("read error (id:%s): %w", c.id, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgType {
|
||||||
|
case websocket.CloseMessage:
|
||||||
|
c.Close(websocket.CloseNormalClosure, "Client closed")
|
||||||
|
return
|
||||||
|
case websocket.TextMessage:
|
||||||
|
if c.OnMessage != nil {
|
||||||
|
c.OnMessage(msg)
|
||||||
|
} else {
|
||||||
|
log.Printf("Received message but no handler set (id:%s): %s", c.id, string(msg))
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
log.Printf("Unhandled message type %d (id:%s)", msgType, c.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Write() {
|
||||||
|
defer c.conn.Close()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case message, ok := <-c.send:
|
||||||
|
c.conn.SetWriteDeadline(time.Now().Add(writeWait))
|
||||||
|
if !ok {
|
||||||
|
// The hub closed the channel.
|
||||||
|
if err := c.conn.WriteMessage(websocket.CloseMessage, []byte("ping")); err != nil {
|
||||||
|
c.handleError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.handleError(fmt.Errorf("server %s closed channel", c.id))
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
if err := c.conn.WriteMessage(websocket.TextMessage, message); err != nil {
|
||||||
|
c.handleError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case message := <-c.unregister:
|
||||||
|
c.conn.WriteMessage(websocket.CloseMessage, message)
|
||||||
|
c.connected = false
|
||||||
|
close(c.send)
|
||||||
|
delete(Broadcast, c.id)
|
||||||
|
close(c.unregister)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) PingInterval(interval time.Duration) {
|
||||||
|
ticker := time.NewTicker(interval)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
if err := c.conn.WriteControl(websocket.PingMessage, []byte("ping"), time.Now().Add(pongWait)); err != nil {
|
||||||
|
c.OnError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for range ticker.C {
|
||||||
|
if c.OnPing != nil {
|
||||||
|
c.OnPing()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.conn.WriteControl(websocket.PingMessage, []byte("ping"), time.Now().Add(pongWait)); err != nil {
|
||||||
|
c.OnError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SendData(data []byte) {
|
||||||
|
if !c.connected {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case c.send <- data:
|
||||||
|
// sent successfully
|
||||||
|
default:
|
||||||
|
// channel full, drop or log
|
||||||
|
if c.OnWarning != nil {
|
||||||
|
c.OnWarning("Dropping message: channel full")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Close(code int, reason string) error {
|
||||||
|
closeMsg := websocket.FormatCloseMessage(code, reason)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case c.unregister <- closeMsg: // Attempt to send
|
||||||
|
default: // If the channel is full, this runs
|
||||||
|
return fmt.Errorf("attempt close client socket failed")
|
||||||
|
}
|
||||||
|
if c.OnClose != nil {
|
||||||
|
c.OnClose(code, reason)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) handleError(err error) {
|
||||||
|
if c.OnError != nil {
|
||||||
|
c.OnError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.Close(websocket.CloseInternalServerErr, err.Error()); err != nil {
|
||||||
|
if c.OnError != nil {
|
||||||
|
c.OnError(err)
|
||||||
|
} else {
|
||||||
|
fmt.Println("error: ", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SendInfo(data any) error {
|
||||||
|
var payload pubSubModels.Data
|
||||||
|
payload.Action = "publish"
|
||||||
|
payload.Topic = fmt.Sprintf("%s/info", c.id)
|
||||||
|
payload.Data = data
|
||||||
|
|
||||||
|
b, err := json.Marshal(payload)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.SendData(b)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SendStatus(data any) error {
|
||||||
|
var payload pubSubModels.Data
|
||||||
|
payload.Action = "publish"
|
||||||
|
payload.Topic = fmt.Sprintf("%s/status", c.id)
|
||||||
|
payload.Data = data
|
||||||
|
|
||||||
|
b, err := json.Marshal(payload)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.SendData(b)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SendDebug(data any) error {
|
||||||
|
var payload pubSubModels.Data
|
||||||
|
payload.Action = "publish"
|
||||||
|
payload.Topic = fmt.Sprintf("%s/debug", c.id)
|
||||||
|
payload.Data = data
|
||||||
|
|
||||||
|
b, err := json.Marshal(payload)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.SendData(b)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SendWarning(data any) error {
|
||||||
|
var payload pubSubModels.Data
|
||||||
|
payload.Action = "publish"
|
||||||
|
payload.Topic = fmt.Sprintf("%s/warning", c.id)
|
||||||
|
payload.Data = data
|
||||||
|
|
||||||
|
b, err := json.Marshal(payload)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.SendData(b)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SendError(data any) error {
|
||||||
|
var payload pubSubModels.Data
|
||||||
|
payload.Action = "publish"
|
||||||
|
payload.Topic = fmt.Sprintf("%s/error", c.id)
|
||||||
|
payload.Data = data
|
||||||
|
|
||||||
|
b, err := json.Marshal(payload)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.SendData(b)
|
||||||
|
return nil
|
||||||
|
}
|
7
models/clients.go
Normal file
7
models/clients.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
type Clients map[string]*Client
|
||||||
|
|
||||||
|
func NewClients() Clients {
|
||||||
|
return make(Clients)
|
||||||
|
}
|
42
models/config.go
Normal file
42
models/config.go
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gitea.tecamino.com/paadi/tecamino-logger/logging"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
AllowOrigins []string
|
||||||
|
Ip string
|
||||||
|
Port int
|
||||||
|
Log logging.Config
|
||||||
|
PubSub PubSub
|
||||||
|
}
|
||||||
|
|
||||||
|
type PubSub struct {
|
||||||
|
Workers int
|
||||||
|
MaxMessages int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) Default() {
|
||||||
|
if len(c.AllowOrigins) == 0 {
|
||||||
|
c.AllowOrigins = []string{"*"}
|
||||||
|
}
|
||||||
|
if c.Ip == "" {
|
||||||
|
c.Ip = "0.0.0.0"
|
||||||
|
}
|
||||||
|
if c.Log.MaxSize == 0 {
|
||||||
|
c.Log.MaxSize = 3
|
||||||
|
}
|
||||||
|
if c.Log.MaxBackup == 0 {
|
||||||
|
c.Log.MaxBackup = 3
|
||||||
|
}
|
||||||
|
if c.Log.MaxAge == 0 {
|
||||||
|
c.Log.MaxAge = 30
|
||||||
|
}
|
||||||
|
if c.PubSub.Workers == 0 {
|
||||||
|
c.PubSub.Workers = 5
|
||||||
|
}
|
||||||
|
if c.PubSub.MaxMessages == 0 {
|
||||||
|
c.PubSub.MaxMessages = 256
|
||||||
|
}
|
||||||
|
}
|
150
statusClient.go
Normal file
150
statusClient.go
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
package statusServer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"statusServer/models"
|
||||||
|
|
||||||
|
pubSubModels "gitea.tecamino.com/paadi/pubSub/models"
|
||||||
|
logging "gitea.tecamino.com/paadi/tecamino-logger/logging"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StatusServerClient wraps a websocket client connection to the StatusServer.
|
||||||
|
// - `id` is the client identifier (service name)
|
||||||
|
// - `client` is the underlying websocket client
|
||||||
|
// - `subs` is a map of topic → callback function, used when subscribed messages are received
|
||||||
|
type StatusClient struct {
|
||||||
|
id string
|
||||||
|
client *models.Client
|
||||||
|
subs map[string]func(data any)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCStatuslient connects to a StatusServer websocket endpoint and sets up callbacks.
|
||||||
|
// serviceName: unique ID/name for this client
|
||||||
|
// ip, port: target server address
|
||||||
|
// debug: enable debug logging
|
||||||
|
func NewStatusClient(serviceName, ip string, port uint, debug bool) (*StatusClient, error) {
|
||||||
|
config := models.Config{Log: logging.Config{Debug: debug}}
|
||||||
|
config.Default()
|
||||||
|
|
||||||
|
// create logger for this client
|
||||||
|
logger, err := logging.NewLogger("statusServer.log", &config.Log)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Debug("NewClient", fmt.Sprintf("initialize new client id:%s to %s:%d", serviceName, ip, port))
|
||||||
|
|
||||||
|
// build client wrapper
|
||||||
|
sc := StatusClient{
|
||||||
|
id: serviceName,
|
||||||
|
subs: map[string]func(data any){}, // no subscriptions yet
|
||||||
|
}
|
||||||
|
|
||||||
|
// connect underlying websocket client
|
||||||
|
sc.client, err = models.NewClient(ip, serviceName, port, logger)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- EVENT HANDLERS ---
|
||||||
|
|
||||||
|
// connection opened
|
||||||
|
sc.client.OnOpen = func() {
|
||||||
|
logger.Info("Client", fmt.Sprintf("id:%s connected to %s:%d", serviceName, ip, port))
|
||||||
|
}
|
||||||
|
|
||||||
|
// message received from server
|
||||||
|
sc.client.OnMessage = func(data []byte) {
|
||||||
|
var p pubSubModels.Data
|
||||||
|
err := json.Unmarshal(data, &p)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("OnMessage", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we have a subscription callback for this topic, call it
|
||||||
|
if f, ok := sc.subs[p.Topic]; ok {
|
||||||
|
f(p.Data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// warning received
|
||||||
|
sc.client.OnWarning = func(warn string) {
|
||||||
|
logger.Warning("Client", warn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// error received
|
||||||
|
sc.client.OnError = func(err error) {
|
||||||
|
logger.Error("Client", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// connection closed
|
||||||
|
sc.client.OnClose = func(code int, reason string) {
|
||||||
|
logger.Info("Client", fmt.Sprintf("id:%s closed connection to %s:%d", serviceName, ip, port))
|
||||||
|
}
|
||||||
|
|
||||||
|
return &sc, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscribe sends a "subscribe" action for a topic to the server and registers
|
||||||
|
// a local callback to handle messages for that topic.
|
||||||
|
func (sc *StatusClient) Subscribe(topic string, cb func(any)) error {
|
||||||
|
var data pubSubModels.Data
|
||||||
|
data.Action = "subscribe"
|
||||||
|
data.Topic = topic
|
||||||
|
|
||||||
|
// send subscribe request to server
|
||||||
|
b, err := json.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sc.client.SendData(b)
|
||||||
|
|
||||||
|
// register callback for topic
|
||||||
|
sc.subs[topic] = cb
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Publish sends a "publish" action with a payload to the server.
|
||||||
|
// The server will then deliver this message to all subscribers of the topic.
|
||||||
|
func (sc *StatusClient) Publish(topic string, data any) error {
|
||||||
|
var payload pubSubModels.Data
|
||||||
|
payload.Action = "publish"
|
||||||
|
payload.Topic = topic
|
||||||
|
payload.Data = data
|
||||||
|
|
||||||
|
b, err := json.Marshal(payload)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sc.client.SendData(b)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Sending helpers (proxy to server messaging system) ---
|
||||||
|
|
||||||
|
// SendInfo sends an "info" message to the server.
|
||||||
|
func (sc *StatusClient) SendInfo(data any) error {
|
||||||
|
return sc.client.SendInfo(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendInfo sends an "status" message to the server.
|
||||||
|
func (sc *StatusClient) SendStatus(data any) error {
|
||||||
|
return sc.client.SendStatus(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendDebug sends a "debug" message to the server.
|
||||||
|
func (sc *StatusClient) SendDebug(data any) error {
|
||||||
|
return sc.client.SendDebug(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendWarning sends a "warning" message to the server.
|
||||||
|
func (sc *StatusClient) SendWarning(data any) error {
|
||||||
|
return sc.client.SendWarning(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendError sends an "error" message to the server.
|
||||||
|
func (sc *StatusClient) SendError(data any) error {
|
||||||
|
return sc.client.SendError(data)
|
||||||
|
}
|
164
statusServer.go
Normal file
164
statusServer.go
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
package statusServer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"runtime"
|
||||||
|
"statusServer/utils"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"statusServer/models"
|
||||||
|
|
||||||
|
logging "gitea.tecamino.com/paadi/tecamino-logger/logging"
|
||||||
|
"github.com/gin-contrib/cors"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StatusServer wraps an HTTP server with PubSub over websockets.
|
||||||
|
// It manages HTTP routes, PubSub broadcasting, and runtime metrics publishing.
|
||||||
|
type StatusServer struct {
|
||||||
|
Ip string // listen address (defaults to all interfaces)
|
||||||
|
Port int // server port
|
||||||
|
Routes *gin.Engine // Gin router for HTTP endpoints
|
||||||
|
pubSub *StatusWebsocket // websocket-enabled PubSub system
|
||||||
|
log *logging.Logger // application logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStatusServer initializes and configures a new StatusServer instance.
|
||||||
|
//
|
||||||
|
// Setup steps:
|
||||||
|
// - Apply default values to the config.
|
||||||
|
// - Initialize logging.
|
||||||
|
// - Configure CORS middleware (including localhost + detected local IP).
|
||||||
|
// - Create PubSubWebsocket with worker/message limits.
|
||||||
|
// - Register the /status websocket endpoint.
|
||||||
|
//
|
||||||
|
// Returns the server instance or an error if initialization fails.
|
||||||
|
func NewStatusServer(config models.Config) (*StatusServer, error) {
|
||||||
|
config.Default()
|
||||||
|
|
||||||
|
// initialize logger
|
||||||
|
logger, err := logging.NewLogger("statusServer.log", &config.Log)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Debug("NewStatusServer", "initialize new server with allowOrigins: "+
|
||||||
|
strings.Join(config.AllowOrigins, ", ")+" listening on port: "+fmt.Sprint(config.Port))
|
||||||
|
|
||||||
|
r := gin.Default()
|
||||||
|
|
||||||
|
// always allow local dev origins (http://localhost:PORT)
|
||||||
|
config.AllowOrigins = append(config.AllowOrigins, fmt.Sprintf("http://localhost:%d", config.Port))
|
||||||
|
|
||||||
|
// detect and allow local IP in CORS origins
|
||||||
|
localIP, err := utils.GetLocalIP()
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("NewStatusServer", "get local ip: "+err.Error())
|
||||||
|
} else {
|
||||||
|
config.AllowOrigins = append(config.AllowOrigins, fmt.Sprintf("http://%s:%d", localIP, config.Port))
|
||||||
|
}
|
||||||
|
|
||||||
|
// configure CORS middleware
|
||||||
|
r.Use(cors.New(cors.Config{
|
||||||
|
AllowOrigins: config.AllowOrigins,
|
||||||
|
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
|
||||||
|
AllowHeaders: []string{"Origin", "Content-Type", "Accept"},
|
||||||
|
ExposeHeaders: []string{"Content-Length"},
|
||||||
|
AllowCredentials: true,
|
||||||
|
MaxAge: 12 * time.Hour,
|
||||||
|
}))
|
||||||
|
|
||||||
|
// build the server
|
||||||
|
s := &StatusServer{
|
||||||
|
Ip: "0.0.0.0", // listen on all interfaces
|
||||||
|
Port: config.Port,
|
||||||
|
Routes: r,
|
||||||
|
log: logger,
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize PubSub websocket service
|
||||||
|
logger.Debug("NewStatusServer", fmt.Sprintf(
|
||||||
|
"initialize new PubSubWebsocket service with %d workers %d messages",
|
||||||
|
config.PubSub.Workers, config.PubSub.MaxMessages,
|
||||||
|
))
|
||||||
|
s.pubSub, err = NewStatusWebsocket(config.PubSub.Workers, config.PubSub.MaxMessages, logger)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("NewPubSubWebsocket", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// register websocket endpoint
|
||||||
|
r.GET("/status", s.pubSub.NewConection)
|
||||||
|
r.GET("/info", s.pubSub.NewConection)
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeHttp starts the HTTP server and continuously publishes runtime metrics
|
||||||
|
// (memory allocation + GC count) to the "info" topic.
|
||||||
|
//
|
||||||
|
// It blocks until the HTTP server stops. Resources are cleaned up on shutdown:
|
||||||
|
// PubSub is closed and a shutdown message is logged.
|
||||||
|
func (s *StatusServer) ServeHttp() error {
|
||||||
|
s.log.Debug("ServeHttp", "start publishing runtime metrics (memory + GC count)")
|
||||||
|
|
||||||
|
// create cancellable context for metrics goroutine
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// metrics publisher goroutine
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
var init bool
|
||||||
|
var m runtime.MemStats
|
||||||
|
var oldM uint64
|
||||||
|
var oldGc uint32
|
||||||
|
var info struct {
|
||||||
|
Memory uint64 `json:"memory"`
|
||||||
|
GC uint32 `json:"gc"`
|
||||||
|
}
|
||||||
|
|
||||||
|
ticker := time.NewTicker(500 * time.Millisecond)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
// stop publishing if context is canceled
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
// read runtime memory statistics
|
||||||
|
runtime.ReadMemStats(&m)
|
||||||
|
info.GC = m.NumGC
|
||||||
|
|
||||||
|
// publish only when values changed or first run
|
||||||
|
if oldGc != m.NumGC || oldM != m.Alloc/1024 || !init {
|
||||||
|
info.Memory = m.Alloc / 1024
|
||||||
|
s.pubSub.Publish("info", info)
|
||||||
|
oldGc = m.NumGC
|
||||||
|
oldM = m.Alloc / 1024
|
||||||
|
init = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// log startup
|
||||||
|
s.log.Info("ServeHttp", fmt.Sprintf("start listening on %s:%d", s.Ip, s.Port))
|
||||||
|
|
||||||
|
// configure HTTP server
|
||||||
|
srv := &http.Server{
|
||||||
|
Addr: fmt.Sprintf("%s:%d", s.Ip, s.Port),
|
||||||
|
Handler: s.Routes,
|
||||||
|
}
|
||||||
|
|
||||||
|
// log shutdown + close PubSub on exit
|
||||||
|
defer s.log.Info("ServeHttp", fmt.Sprintf("close server listening on %s:%d", s.Ip, s.Port))
|
||||||
|
defer s.pubSub.Close()
|
||||||
|
|
||||||
|
// blocking call
|
||||||
|
return srv.ListenAndServe()
|
||||||
|
}
|
132
statusWebsocket.go
Normal file
132
statusWebsocket.go
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
package statusServer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"statusServer/handlers"
|
||||||
|
|
||||||
|
logging "gitea.tecamino.com/paadi/tecamino-logger/logging"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"gitea.tecamino.com/paadi/pubSub"
|
||||||
|
pubSubModels "gitea.tecamino.com/paadi/pubSub/models"
|
||||||
|
"gitea.tecamino.com/paadi/tecamino-dbm/auth"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StatusWebsocket wraps a PubSub instance with websocket support.
|
||||||
|
// It manages connected clients, handles their subscriptions, and
|
||||||
|
// relays messages via a PubSub publish/subscribe mechanism.
|
||||||
|
type StatusWebsocket struct {
|
||||||
|
*pubSub.Pubsub // core publish/subscribe engine
|
||||||
|
clientHandler *handlers.ClientHandler // manages websocket clients
|
||||||
|
*logging.Logger // application logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStatusWebsocket initializes a new PubSubWebsocket server.
|
||||||
|
// - worker: number of worker goroutines processing messages.
|
||||||
|
// - maxMessage: size of the message queue.
|
||||||
|
// - logger: optional custom logger (creates default if nil).
|
||||||
|
func NewStatusWebsocket(worker, maxMessage int, logger *logging.Logger) (ws *StatusWebsocket, err error) {
|
||||||
|
if logger == nil {
|
||||||
|
// Create default logger if none provided.
|
||||||
|
logger, err = logging.NewLogger("PubSubWebsocket.log", logging.DefaultConfig())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ws = &StatusWebsocket{
|
||||||
|
Pubsub: pubSub.NewPubsub(worker, maxMessage),
|
||||||
|
clientHandler: handlers.NewConnectionHandler(),
|
||||||
|
Logger: logger,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConection upgrades an HTTP request to a websocket connection
|
||||||
|
// and registers the client with the PubSubWebsocket server.
|
||||||
|
//
|
||||||
|
// Each connected client can:
|
||||||
|
// - subscribe to topics (receives messages published on them),
|
||||||
|
// - unsubscribe from topics,
|
||||||
|
// - publish messages to topics (delivered to all subscribers).
|
||||||
|
func (ws *StatusWebsocket) NewConection(c *gin.Context) {
|
||||||
|
// Authenticate and extract client ID from query parameters.
|
||||||
|
id, err := auth.GetIDFromQuery(c)
|
||||||
|
if err != nil {
|
||||||
|
ws.Error("Websocket", "error GetIDFromQuery: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ws.Debug("Websocket", "authorization id token: "+id)
|
||||||
|
|
||||||
|
// Create or reuse a websocket client.
|
||||||
|
client, err := ws.clientHandler.ConnectNewClient(id, c)
|
||||||
|
if err != nil {
|
||||||
|
ws.Error("Websocket", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------- Event Handlers ----------------
|
||||||
|
|
||||||
|
// Handle incoming messages from the client.
|
||||||
|
client.OnMessage = func(data []byte) {
|
||||||
|
// Decode request into PubSub message model.
|
||||||
|
request, err := readJsonData(data)
|
||||||
|
if err != nil {
|
||||||
|
client.SendData([]byte(`{"error":"read json: ` + err.Error() + `"}`))
|
||||||
|
ws.Error("Websocket", "read json: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Route the action type.
|
||||||
|
switch request.Action {
|
||||||
|
case "subscribe":
|
||||||
|
ws.Debug("Websocket", "subscribe id:"+id+" topic:"+request.Topic)
|
||||||
|
|
||||||
|
// Register subscription callback.
|
||||||
|
ws.Subscribe(id, request.Topic, func(data any) {
|
||||||
|
b, err := json.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
client.SendData(b)
|
||||||
|
})
|
||||||
|
|
||||||
|
case "unsubscribe":
|
||||||
|
ws.Debug("Websocket", "unsubscribe id:"+id+" topic:"+request.Topic)
|
||||||
|
ws.Unsubscribe(id, request.Topic)
|
||||||
|
|
||||||
|
case "publish":
|
||||||
|
ws.Publish(request.Topic, request.Data)
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Invalid or unsupported action.
|
||||||
|
ws.Error("Websocket", "action type '"+request.Action+"' not supported")
|
||||||
|
client.SendData([]byte(`{"error":"action type '` + request.Action + `' not supported"}`))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle warnings raised by the client connection.
|
||||||
|
client.OnWarning = func(msg string) {
|
||||||
|
ws.Warning("Websocket", "warning on websocket connection: "+msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle errors raised by the client connection.
|
||||||
|
client.OnError = func(err error) {
|
||||||
|
ws.Error("Websocket", "error on websocket connection: "+err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup when the client disconnects.
|
||||||
|
client.OnClose = func(code int, reason string) {
|
||||||
|
ws.Debug("Websocket", fmt.Sprintf("onClose id:%s code:%d reason:%s", id, code, reason))
|
||||||
|
ws.UnsubscribeAll(id) // free all subscriptions for this client
|
||||||
|
ws.clientHandler.RemoveClient(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// readJsonData decodes raw websocket bytes into a PubSub Data struct.
|
||||||
|
func readJsonData(data []byte) (request pubSubModels.Data, err error) {
|
||||||
|
err = json.Unmarshal(data, &request)
|
||||||
|
return
|
||||||
|
}
|
60
test/server_test.go
Normal file
60
test/server_test.go
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"statusServer"
|
||||||
|
"statusServer/models"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gitea.tecamino.com/paadi/tecamino-logger/logging"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestServer(t *testing.T) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
server, err := statusServer.NewStatusServer(models.Config{Port: 8080})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
err = server.ServeHttp()
|
||||||
|
}()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConnection(t *testing.T) {
|
||||||
|
s, err := statusServer.NewStatusServer(models.Config{Port: 8080, Log: logging.Config{Debug: true}})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
go s.ServeHttp()
|
||||||
|
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
|
client1, err := statusServer.NewStatusClient("adrian", "127.0.0.1", 8080, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
client, err := statusServer.NewStatusClient("test", "127.0.0.1", 8080, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client1.Subscribe("test/info", func(a any) {
|
||||||
|
t.Log("test", a)
|
||||||
|
})
|
||||||
|
var info struct {
|
||||||
|
Message string
|
||||||
|
Count int
|
||||||
|
}
|
||||||
|
info.Message = "test count"
|
||||||
|
for range 10 {
|
||||||
|
info.Count++
|
||||||
|
client.SendInfo(info)
|
||||||
|
client.SendInfo("info")
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
}
|
||||||
|
}
|
316
test/statusServer.log
Normal file
316
test/statusServer.log
Normal file
@@ -0,0 +1,316 @@
|
|||||||
|
{"level":"info","timestamp":"2025-08-23T10:24:46.703","msg":"start listening on 0.0.0.0:8080","caller":"ServeHttp"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T10:26:29.040","msg":"initialize new server with allowOrigins: * listening on port: 8080","caller":"NewStatusServer"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T10:26:29.040","msg":"start listening on 0.0.0.0:8080","caller":"ServeHttp"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T10:26:29.040","msg":"initialize new PubSub service with 5 workers 256 messages","caller":"NewStatusServer"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T10:26:29.040","msg":"start example variables like memory usage and garbage collection count","caller":"ServeHttp"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T10:26:29.040","msg":"start listening on 0.0.0.0:8080","caller":"ServeHttp"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T10:26:29.041","msg":"close server listening on 0.0.0.0:8080","caller":"ServeHttp"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T10:26:30.041","msg":"initialize new client id:adrian to 127.0.0.1:8080","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T10:26:30.043","msg":"set PingHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T10:26:30.043","msg":"set PongHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T10:26:30.043","msg":"start read goroutine","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T10:26:30.043","msg":"start write goroutine","caller":"NewClient"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T10:26:30.043","msg":"id:adrian connected to 127.0.0.1:8080","caller":"Client"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T10:26:30.043","msg":"initialize new client id:test to 127.0.0.1:8080","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T10:26:30.044","msg":"set PingHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T10:26:30.044","msg":"set PongHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T10:26:30.044","msg":"start read goroutine","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T10:26:30.044","msg":"start write goroutine","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T10:28:36.617","msg":"initialize new server with allowOrigins: * listening on port: 8080","caller":"NewStatusServer"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T10:28:36.618","msg":"start listening on 0.0.0.0:8080","caller":"ServeHttp"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T10:28:36.618","msg":"initialize new PubSub service with 5 workers 256 messages","caller":"NewStatusServer"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T10:28:36.618","msg":"start example variables like memory usage and garbage collection count","caller":"ServeHttp"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T10:28:36.618","msg":"start listening on 0.0.0.0:8080","caller":"ServeHttp"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T10:28:36.619","msg":"close server listening on 0.0.0.0:8080","caller":"ServeHttp"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T10:28:37.619","msg":"initialize new client id:adrian to 127.0.0.1:8080","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T10:28:37.621","msg":"authorization id token: adrian","caller":"Websocket"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T10:28:37.622","msg":"set PingHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T10:28:37.622","msg":"set PongHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T10:28:37.622","msg":"start read goroutine","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T10:28:37.622","msg":"start write goroutine","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T10:28:37.622","msg":"initialize new client id:test to 127.0.0.1:8080","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T10:28:37.622","msg":"authorization id token: test","caller":"Websocket"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T10:28:37.623","msg":"set PingHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T10:28:37.623","msg":"set PongHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T10:28:37.623","msg":"start read goroutine","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T10:28:37.623","msg":"start write goroutine","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T10:28:37.624","msg":"subscribe id:adrian topic:test/info","caller":"Websocket"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T20:48:20.110","msg":"start listening on 0.0.0.0:8080","caller":"ServeHttp"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:48:20.110","msg":"initialize new server with allowOrigins: * listening on port: 8080","caller":"NewStatusServer"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:48:20.111","msg":"initialize new status server server","caller":"NewStatusServer"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:48:20.111","msg":"initialize new PubSubWebsocket service with 5 workers 256 messages","caller":"NewStatusServer"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:48:20.111","msg":"start example variables like memory usage and garbage collection count","caller":"ServeHttp"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T20:48:20.111","msg":"start listening on 0.0.0.0:8080","caller":"ServeHttp"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T20:48:20.111","msg":"close server listening on 0.0.0.0:8080","caller":"ServeHttp"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:48:21.111","msg":"initialize new client id:adrian to 127.0.0.1:8080","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:48:21.114","msg":"set PingHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:48:21.114","msg":"set PongHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:48:21.114","msg":"start read goroutine","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:48:21.114","msg":"start write goroutine","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:48:21.114","msg":"initialize new client id:test to 127.0.0.1:8080","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:48:21.114","msg":"set PingHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:48:21.114","msg":"set PongHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:48:21.114","msg":"start read goroutine","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:48:21.115","msg":"start write goroutine","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:54:32.958","msg":"initialize new server with allowOrigins: * listening on port: 8080","caller":"NewStatusServer"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T20:54:32.958","msg":"start listening on 0.0.0.0:8080","caller":"ServeHttp"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:54:32.959","msg":"initialize new PubSubWebsocket service with 5 workers 256 messages","caller":"NewStatusServer"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:54:32.959","msg":"start publishing runtime metrics (memory + GC count)","caller":"ServeHttp"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T20:54:32.959","msg":"start listening on 0.0.0.0:8080","caller":"ServeHttp"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T20:54:32.960","msg":"close server listening on 0.0.0.0:8080","caller":"ServeHttp"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:54:33.960","msg":"initialize new client id:adrian to 127.0.0.1:8080","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:54:33.961","msg":"set PingHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:54:33.961","msg":"set PongHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:54:33.961","msg":"start read goroutine","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:54:33.961","msg":"start write goroutine","caller":"NewClient"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T20:54:33.961","msg":"id:adrian connected to 127.0.0.1:8080","caller":"Client"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:54:33.961","msg":"initialize new client id:test to 127.0.0.1:8080","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:54:33.961","msg":"set PingHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:54:33.961","msg":"set PongHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:54:33.961","msg":"start read goroutine","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:54:33.961","msg":"start write goroutine","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:54:52.574","msg":"initialize new server with allowOrigins: * listening on port: 8080","caller":"NewStatusServer"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T20:54:52.574","msg":"start listening on 0.0.0.0:8080","caller":"ServeHttp"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:54:52.575","msg":"initialize new PubSubWebsocket service with 5 workers 256 messages","caller":"NewStatusServer"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:54:52.575","msg":"start publishing runtime metrics (memory + GC count)","caller":"ServeHttp"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T20:54:52.575","msg":"start listening on 0.0.0.0:8080","caller":"ServeHttp"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T20:54:52.575","msg":"close server listening on 0.0.0.0:8080","caller":"ServeHttp"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:54:53.311","msg":"authorization id token: test","caller":"Websocket"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:54:53.576","msg":"initialize new client id:adrian to 127.0.0.1:8080","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:54:53.577","msg":"authorization id token: adrian","caller":"Websocket"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:54:53.577","msg":"set PingHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:54:53.577","msg":"set PongHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:54:53.577","msg":"start read goroutine","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:54:53.577","msg":"start write goroutine","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:54:53.577","msg":"initialize new client id:test to 127.0.0.1:8080","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:54:53.577","msg":"authorization id token: test","caller":"Websocket"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:55:00.048","msg":"initialize new server with allowOrigins: * listening on port: 8080","caller":"NewStatusServer"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T20:55:00.048","msg":"start listening on 0.0.0.0:8080","caller":"ServeHttp"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:55:00.049","msg":"initialize new PubSubWebsocket service with 5 workers 256 messages","caller":"NewStatusServer"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:55:00.049","msg":"start publishing runtime metrics (memory + GC count)","caller":"ServeHttp"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T20:55:00.049","msg":"start listening on 0.0.0.0:8080","caller":"ServeHttp"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T20:55:00.049","msg":"close server listening on 0.0.0.0:8080","caller":"ServeHttp"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:55:01.050","msg":"initialize new client id:adrian to 127.0.0.1:8080","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:55:01.052","msg":"authorization id token: adrian","caller":"Websocket"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:55:01.052","msg":"set PingHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:55:01.052","msg":"set PongHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:55:01.052","msg":"start read goroutine","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:55:01.052","msg":"start write goroutine","caller":"NewClient"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T20:55:01.052","msg":"id:adrian connected to 127.0.0.1:8080","caller":"Client"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:55:01.052","msg":"initialize new client id:test to 127.0.0.1:8080","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:55:01.053","msg":"authorization id token: test","caller":"Websocket"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:55:01.053","msg":"set PingHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:55:01.053","msg":"set PongHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:55:01.053","msg":"start read goroutine","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:55:01.053","msg":"start write goroutine","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:55:01.053","msg":"subscribe id:adrian topic:test/info","caller":"Websocket"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:55:02.176","msg":"authorization id token: test","caller":"Websocket"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:55:04.610","msg":"authorization id token: test","caller":"Websocket"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:55:05.111","msg":"authorization id token: test","caller":"Websocket"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:55:05.391","msg":"authorization id token: test","caller":"Websocket"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:55:06.443","msg":"authorization id token: test","caller":"Websocket"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:55:07.210","msg":"authorization id token: test","caller":"Websocket"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:55:08.226","msg":"authorization id token: test","caller":"Websocket"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:55:47.859","msg":"initialize new server with allowOrigins: * listening on port: 8080","caller":"NewStatusServer"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T20:55:47.859","msg":"start listening on 0.0.0.0:8080","caller":"ServeHttp"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:55:47.859","msg":"initialize new PubSubWebsocket service with 5 workers 256 messages","caller":"NewStatusServer"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:55:47.860","msg":"start publishing runtime metrics (memory + GC count)","caller":"ServeHttp"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T20:55:47.860","msg":"start listening on 0.0.0.0:8080","caller":"ServeHttp"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T20:55:47.860","msg":"close server listening on 0.0.0.0:8080","caller":"ServeHttp"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:55:48.860","msg":"initialize new client id:adrian to 127.0.0.1:8080","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:55:48.862","msg":"set PingHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:55:48.862","msg":"set PongHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:55:48.862","msg":"start read goroutine","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:55:48.862","msg":"start write goroutine","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:55:48.862","msg":"initialize new client id:test to 127.0.0.1:8080","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:55:48.863","msg":"set PingHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:55:48.863","msg":"set PongHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:55:48.863","msg":"start read goroutine","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:55:48.863","msg":"start write goroutine","caller":"NewClient"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T20:55:48.863","msg":"id:test connected to 127.0.0.1:8080","caller":"Client"}
|
||||||
|
{"level":"error","timestamp":"2025-08-23T20:56:05.391","msg":"error on websocket connection: read error (id:test34): websocket: close 1000 (normal)","caller":"Websocket"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:56:39.215","msg":"initialize new server with allowOrigins: * listening on port: 8080","caller":"NewStatusServer"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T20:56:39.215","msg":"start listening on 0.0.0.0:8080","caller":"ServeHttp"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:56:39.215","msg":"initialize new PubSubWebsocket service with 5 workers 256 messages","caller":"NewStatusServer"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:56:39.216","msg":"start publishing runtime metrics (memory + GC count)","caller":"ServeHttp"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T20:56:39.216","msg":"start listening on 0.0.0.0:8080","caller":"ServeHttp"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T20:56:39.216","msg":"close server listening on 0.0.0.0:8080","caller":"ServeHttp"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:56:40.216","msg":"initialize new client id:adrian to 127.0.0.1:8080","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:56:40.218","msg":"set PingHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:56:40.218","msg":"set PongHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:56:40.218","msg":"start read goroutine","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:56:40.218","msg":"start write goroutine","caller":"NewClient"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T20:56:40.218","msg":"id:adrian connected to 127.0.0.1:8080","caller":"Client"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:56:40.218","msg":"initialize new client id:test to 127.0.0.1:8080","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:56:40.219","msg":"set PingHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:56:40.219","msg":"set PongHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:56:40.219","msg":"start read goroutine","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:56:40.219","msg":"start write goroutine","caller":"NewClient"}
|
||||||
|
{"level":"error","timestamp":"2025-08-23T20:56:40.877","msg":"error GetIDFromQuery: id missing","caller":"Websocket"}
|
||||||
|
{"level":"error","timestamp":"2025-08-23T20:56:42.110","msg":"error GetIDFromQuery: id missing","caller":"Websocket"}
|
||||||
|
{"level":"error","timestamp":"2025-08-23T20:56:43.644","msg":"error GetIDFromQuery: id missing","caller":"Websocket"}
|
||||||
|
{"level":"error","timestamp":"2025-08-23T20:56:48.094","msg":"error GetIDFromQuery: id missing","caller":"Websocket"}
|
||||||
|
{"level":"error","timestamp":"2025-08-23T20:56:48.545","msg":"error GetIDFromQuery: id missing","caller":"Websocket"}
|
||||||
|
{"level":"error","timestamp":"2025-08-23T20:56:49.027","msg":"error GetIDFromQuery: id missing","caller":"Websocket"}
|
||||||
|
{"level":"error","timestamp":"2025-08-23T20:56:58.724","msg":"error GetIDFromQuery: id missing","caller":"Websocket"}
|
||||||
|
{"level":"error","timestamp":"2025-08-23T20:56:59.092","msg":"error GetIDFromQuery: id missing","caller":"Websocket"}
|
||||||
|
{"level":"error","timestamp":"2025-08-23T20:56:59.292","msg":"error GetIDFromQuery: id missing","caller":"Websocket"}
|
||||||
|
{"level":"error","timestamp":"2025-08-23T20:56:59.424","msg":"error GetIDFromQuery: id missing","caller":"Websocket"}
|
||||||
|
{"level":"error","timestamp":"2025-08-23T20:56:59.608","msg":"error GetIDFromQuery: id missing","caller":"Websocket"}
|
||||||
|
{"level":"error","timestamp":"2025-08-23T20:57:00.443","msg":"error GetIDFromQuery: id missing","caller":"Websocket"}
|
||||||
|
{"level":"error","timestamp":"2025-08-23T20:57:00.626","msg":"error GetIDFromQuery: id missing","caller":"Websocket"}
|
||||||
|
{"level":"error","timestamp":"2025-08-23T20:57:00.809","msg":"error GetIDFromQuery: id missing","caller":"Websocket"}
|
||||||
|
{"level":"error","timestamp":"2025-08-23T20:57:00.991","msg":"error GetIDFromQuery: id missing","caller":"Websocket"}
|
||||||
|
{"level":"error","timestamp":"2025-08-23T20:57:01.157","msg":"error GetIDFromQuery: id missing","caller":"Websocket"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:58:03.738","msg":"initialize new server with allowOrigins: * listening on port: 8080","caller":"NewStatusServer"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T20:58:03.739","msg":"start listening on 0.0.0.0:8080","caller":"ServeHttp"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:58:03.739","msg":"initialize new PubSub service with 5 workers 256 messages","caller":"NewStatusServer"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:58:03.739","msg":"start example variables like memory usage and garbage collection count","caller":"ServeHttp"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T20:58:03.739","msg":"start listening on 0.0.0.0:8080","caller":"ServeHttp"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T20:58:03.740","msg":"close server listening on 0.0.0.0:8080","caller":"ServeHttp"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:58:04.740","msg":"initialize new client id:adrian to 127.0.0.1:8080","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:58:04.741","msg":"set PingHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:58:04.741","msg":"set PongHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:58:04.741","msg":"start read goroutine","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:58:04.741","msg":"start write goroutine","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:58:04.741","msg":"initialize new client id:test to 127.0.0.1:8080","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:58:04.742","msg":"set PingHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:58:04.742","msg":"set PongHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:58:04.742","msg":"start read goroutine","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:58:04.742","msg":"start write goroutine","caller":"NewClient"}
|
||||||
|
{"level":"error","timestamp":"2025-08-23T20:58:05.278","msg":"error GetIDFromQuery: id missing","caller":"Websocket"}
|
||||||
|
{"level":"error","timestamp":"2025-08-23T20:58:06.860","msg":"error GetIDFromQuery: id missing","caller":"Websocket"}
|
||||||
|
{"level":"error","timestamp":"2025-08-23T20:58:10.226","msg":"error GetIDFromQuery: id missing","caller":"Websocket"}
|
||||||
|
{"level":"error","timestamp":"2025-08-23T20:58:10.994","msg":"error GetIDFromQuery: id missing","caller":"Websocket"}
|
||||||
|
{"level":"error","timestamp":"2025-08-23T20:58:11.561","msg":"error GetIDFromQuery: id missing","caller":"Websocket"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:58:47.241","msg":"initialize new server with allowOrigins: * listening on port: 8080","caller":"NewStatusServer"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T20:58:47.241","msg":"start listening on 0.0.0.0:8080","caller":"ServeHttp"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:58:47.242","msg":"initialize new PubSubWebsocket service with 5 workers 256 messages","caller":"NewStatusServer"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:58:47.242","msg":"start publishing runtime metrics (memory + GC count)","caller":"ServeHttp"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T20:58:47.242","msg":"start listening on 0.0.0.0:8080","caller":"ServeHttp"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T20:58:47.242","msg":"close server listening on 0.0.0.0:8080","caller":"ServeHttp"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:58:48.242","msg":"initialize new client id:adrian to 127.0.0.1:8080","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:58:48.244","msg":"set PingHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:58:48.245","msg":"set PongHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:58:48.245","msg":"start read goroutine","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:58:48.245","msg":"start write goroutine","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:58:48.245","msg":"initialize new client id:test to 127.0.0.1:8080","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:58:48.246","msg":"set PingHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:58:48.246","msg":"set PongHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:58:48.246","msg":"start read goroutine","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:58:48.246","msg":"start write goroutine","caller":"NewClient"}
|
||||||
|
{"level":"error","timestamp":"2025-08-23T20:59:02.224","msg":"read json: invalid character '\\n' in string literal","caller":"Websocket"}
|
||||||
|
{"level":"error","timestamp":"2025-08-23T20:59:46.456","msg":"error on websocket connection: read error (id:234): websocket: close 1000 (normal)","caller":"Websocket"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:59:58.891","msg":"initialize new server with allowOrigins: * listening on port: 8080","caller":"NewStatusServer"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T20:59:58.891","msg":"start listening on 0.0.0.0:8080","caller":"ServeHttp"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:59:58.892","msg":"initialize new PubSubWebsocket service with 5 workers 256 messages","caller":"NewStatusServer"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:59:58.892","msg":"start publishing runtime metrics (memory + GC count)","caller":"ServeHttp"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T20:59:58.892","msg":"start listening on 0.0.0.0:8080","caller":"ServeHttp"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T20:59:58.892","msg":"close server listening on 0.0.0.0:8080","caller":"ServeHttp"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:59:59.893","msg":"initialize new client id:adrian to 127.0.0.1:8080","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:59:59.895","msg":"set PingHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:59:59.895","msg":"set PongHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:59:59.895","msg":"start read goroutine","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:59:59.895","msg":"start write goroutine","caller":"NewClient"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T20:59:59.895","msg":"id:adrian connected to 127.0.0.1:8080","caller":"Client"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:59:59.895","msg":"initialize new client id:test to 127.0.0.1:8080","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:59:59.896","msg":"set PingHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:59:59.896","msg":"set PongHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:59:59.896","msg":"start read goroutine","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T20:59:59.896","msg":"start write goroutine","caller":"NewClient"}
|
||||||
|
{"level":"error","timestamp":"2025-08-23T21:00:06.689","msg":"error on websocket connection: read error (id:234): websocket: close 1000 (normal)","caller":"Websocket"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:00:52.670","msg":"initialize new server with allowOrigins: * listening on port: 8080","caller":"NewStatusServer"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T21:00:52.670","msg":"start listening on 0.0.0.0:8080","caller":"ServeHttp"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:00:52.671","msg":"initialize new PubSub service with 5 workers 256 messages","caller":"NewStatusServer"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:00:52.671","msg":"start example variables like memory usage and garbage collection count","caller":"ServeHttp"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T21:00:52.671","msg":"start listening on 0.0.0.0:8080","caller":"ServeHttp"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T21:00:52.671","msg":"close server listening on 0.0.0.0:8080","caller":"ServeHttp"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:00:53.671","msg":"initialize new client id:adrian to 127.0.0.1:8080","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:00:53.674","msg":"set PingHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:00:53.674","msg":"set PongHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:00:53.674","msg":"start read goroutine","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:00:53.674","msg":"start write goroutine","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:00:53.674","msg":"initialize new client id:test to 127.0.0.1:8080","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:00:53.675","msg":"set PingHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:00:53.675","msg":"set PongHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:00:53.675","msg":"start read goroutine","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:00:53.675","msg":"start write goroutine","caller":"NewClient"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T21:00:53.675","msg":"id:test connected to 127.0.0.1:8080","caller":"Client"}
|
||||||
|
{"level":"error","timestamp":"2025-08-23T21:01:01.672","msg":"error on websocket connection: read error (id:234): websocket: close 1000 (normal)","caller":"Websocket"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:02:03.657","msg":"initialize new server with allowOrigins: * listening on port: 8080","caller":"NewStatusServer"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T21:02:03.657","msg":"start listening on 0.0.0.0:8080","caller":"ServeHttp"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:02:03.658","msg":"initialize new PubSubWebsocket service with 5 workers 256 messages","caller":"NewStatusServer"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:02:03.658","msg":"start publishing runtime metrics (memory + GC count)","caller":"ServeHttp"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T21:02:03.658","msg":"start listening on 0.0.0.0:8080","caller":"ServeHttp"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T21:02:03.659","msg":"close server listening on 0.0.0.0:8080","caller":"ServeHttp"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:02:04.659","msg":"initialize new client id:adrian to 127.0.0.1:8080","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:02:04.661","msg":"set PingHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:02:04.661","msg":"set PongHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:02:04.661","msg":"start read goroutine","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:02:04.661","msg":"start write goroutine","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:02:04.661","msg":"initialize new client id:test to 127.0.0.1:8080","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:02:04.661","msg":"set PingHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:02:04.661","msg":"set PongHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:02:04.661","msg":"start read goroutine","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:02:04.661","msg":"start write goroutine","caller":"NewClient"}
|
||||||
|
{"level":"error","timestamp":"2025-08-23T21:02:13.655","msg":"error on websocket connection: read error (id:235): websocket: close 1000 (normal)","caller":"Websocket"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:07:39.871","msg":"initialize new server with allowOrigins: * listening on port: 8080","caller":"NewStatusServer"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T21:07:39.871","msg":"start listening on 0.0.0.0:8080","caller":"ServeHttp"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:07:39.872","msg":"initialize new PubSubWebsocket service with 5 workers 256 messages","caller":"NewStatusServer"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:07:39.872","msg":"start publishing runtime metrics (memory + GC count)","caller":"ServeHttp"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T21:07:39.872","msg":"start listening on 0.0.0.0:8080","caller":"ServeHttp"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T21:07:39.872","msg":"close server listening on 0.0.0.0:8080","caller":"ServeHttp"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:07:40.872","msg":"initialize new client id:adrian to 127.0.0.1:8080","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:07:40.874","msg":"set PingHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:07:40.874","msg":"set PongHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:07:40.874","msg":"start read goroutine","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:07:40.874","msg":"start write goroutine","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:07:40.874","msg":"initialize new client id:test to 127.0.0.1:8080","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:07:40.875","msg":"set PingHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:07:40.875","msg":"set PongHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:07:40.875","msg":"start read goroutine","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:07:40.875","msg":"start write goroutine","caller":"NewClient"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T21:07:40.875","msg":"id:test connected to 127.0.0.1:8080","caller":"Client"}
|
||||||
|
{"level":"error","timestamp":"2025-08-23T21:07:48.105","msg":"error on websocket connection: read error (id:235): websocket: close 1000 (normal)","caller":"Websocket"}
|
||||||
|
{"level":"error","timestamp":"2025-08-23T21:08:02.336","msg":"error on websocket connection: read error (id:235): websocket: close 1000 (normal)","caller":"Websocket"}
|
||||||
|
{"level":"error","timestamp":"2025-08-23T21:08:10.976","msg":"error on websocket connection: websocket: close sent","caller":"Websocket"}
|
||||||
|
{"level":"error","timestamp":"2025-08-23T21:08:17.158","msg":"error on websocket connection: websocket: close sent","caller":"Websocket"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:39:47.673","msg":"initialize new server with allowOrigins: * listening on port: 8080","caller":"NewStatusServer"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T21:39:47.673","msg":"start listening on 0.0.0.0:8080","caller":"ServeHttp"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:39:47.674","msg":"initialize new PubSubWebsocket service with 5 workers 256 messages","caller":"NewStatusServer"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:39:47.674","msg":"start publishing runtime metrics (memory + GC count)","caller":"ServeHttp"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T21:39:47.674","msg":"start listening on 0.0.0.0:8080","caller":"ServeHttp"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T21:39:47.674","msg":"close server listening on 0.0.0.0:8080","caller":"ServeHttp"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:39:48.674","msg":"initialize new client id:adrian to 127.0.0.1:8080","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:39:48.676","msg":"set PingHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:39:48.676","msg":"set PongHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:39:48.676","msg":"start read goroutine","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:39:48.676","msg":"start write goroutine","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:39:48.676","msg":"initialize new client id:test to 127.0.0.1:8080","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:39:48.677","msg":"set PingHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:39:48.677","msg":"set PongHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:39:48.677","msg":"start read goroutine","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:39:48.677","msg":"start write goroutine","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:41:24.365","msg":"initialize new server with allowOrigins: * listening on port: 8080","caller":"NewStatusServer"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T21:41:24.365","msg":"start listening on 0.0.0.0:8080","caller":"ServeHttp"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:41:24.365","msg":"initialize new PubSubWebsocket service with 5 workers 256 messages","caller":"NewStatusServer"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:41:24.365","msg":"start publishing runtime metrics (memory + GC count)","caller":"ServeHttp"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T21:41:24.365","msg":"start listening on 0.0.0.0:8080","caller":"ServeHttp"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T21:41:24.366","msg":"close server listening on 0.0.0.0:8080","caller":"ServeHttp"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:41:25.366","msg":"initialize new client id:adrian to 127.0.0.1:8080","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:41:25.368","msg":"set PingHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:41:25.368","msg":"set PongHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:41:25.368","msg":"start read goroutine","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:41:25.368","msg":"start write goroutine","caller":"NewClient"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T21:41:25.368","msg":"id:adrian connected to 127.0.0.1:8080","caller":"Client"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:41:25.368","msg":"initialize new client id:test to 127.0.0.1:8080","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:41:25.369","msg":"set PingHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:41:25.369","msg":"set PongHandler","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:41:25.369","msg":"start read goroutine","caller":"NewClient"}
|
||||||
|
{"level":"debug","timestamp":"2025-08-23T21:41:25.369","msg":"start write goroutine","caller":"NewClient"}
|
||||||
|
{"level":"info","timestamp":"2025-08-23T21:41:25.369","msg":"id:test connected to 127.0.0.1:8080","caller":"Client"}
|
||||||
|
{"level":"error","timestamp":"2025-08-23T21:42:15.297","msg":"error on websocket connection: read error (id:238): websocket: close 1000 (normal)","caller":"Websocket"}
|
||||||
|
{"level":"error","timestamp":"2025-08-23T21:42:25.420","msg":"error on websocket connection: websocket: close sent","caller":"Websocket"}
|
||||||
|
{"level":"error","timestamp":"2025-08-23T21:42:26.597","msg":"error on websocket connection: read error (id:234): websocket: close 1006 (abnormal closure): unexpected EOF","caller":"Websocket"}
|
||||||
|
{"level":"error","timestamp":"2025-08-23T21:42:26.597","msg":"error on websocket connection: read error (id:238): websocket: close 1006 (abnormal closure): unexpected EOF","caller":"Websocket"}
|
||||||
|
{"level":"error","timestamp":"2025-08-23T21:42:27.701","msg":"error on websocket connection: websocket: close sent","caller":"Websocket"}
|
22
utils/utils.go
Normal file
22
utils/utils.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetLocalIP() (string, error) {
|
||||||
|
addrs, err := net.InterfaceAddrs()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, addr := range addrs {
|
||||||
|
if ipNet, ok := addr.(*net.IPNet); ok && !ipNet.IP.IsLoopback() {
|
||||||
|
if ipNet.IP.To4() != nil {
|
||||||
|
return ipNet.IP.String(), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("no local IP address found")
|
||||||
|
}
|
Reference in New Issue
Block a user