1
1
// Copyright (c) Tailscale Inc & AUTHORS
2
2
// SPDX-License-Identifier: BSD-3-Clause
3
3
4
- // Package sockstatlog provides a logger for capturing and storing network socket stats.
4
+ // Package sockstatlog provides a logger for capturing network socket stats for debugging.
5
+ // Stats are collected at a frequency of 10 Hz and logged to disk.
6
+ // Stats are only uploaded to the log server on demand.
5
7
package sockstatlog
6
8
7
9
import (
@@ -25,19 +27,29 @@ import (
25
27
"tailscale.com/util/mak"
26
28
)
27
29
28
- // pollPeriod specifies how often to poll for socket stats.
29
- const pollPeriod = time .Second / 10
30
+ // pollInterval specifies how often to poll for socket stats.
31
+ const pollInterval = time .Second / 10
32
+
33
+ // logInterval specifies how often to log sockstat events to disk.
34
+ // This delay is added to prevent an infinite loop when logs are uploaded,
35
+ // which itself creates additional sockstat events.
36
+ const logInterval = 3 * time .Second
30
37
31
38
// Logger logs statistics about network sockets.
32
39
type Logger struct {
40
+ // enabled identifies whether the logger is enabled.
33
41
enabled atomic.Bool
34
42
35
43
ctx context.Context
36
44
cancelFn context.CancelFunc
37
45
38
- ticker * time.Ticker
39
- logf logger.Logf
46
+ // eventCh is used to pass events from the poller to the logger.
47
+ eventCh chan event
48
+
49
+ logf logger.Logf
40
50
51
+ // logger is the underlying logtail logger than manages log files on disk
52
+ // and uploading to the log server.
41
53
logger * logtail.Logger
42
54
filch * filch.Filch
43
55
tr http.RoundTripper
@@ -73,6 +85,7 @@ func SockstatLogID(logID logid.PublicID) logid.PrivateID {
73
85
// NewLogger returns a new Logger that will store stats in logdir.
74
86
// On platforms that do not support sockstat logging, a nil Logger will be returned.
75
87
// The returned Logger is not yet enabled, and must be shut down with Shutdown when it is no longer needed.
88
+ // Logs will be uploaded to the log server using a new log ID derived from the provided backend logID.
76
89
func NewLogger (logdir string , logf logger.Logf , logID logid.PublicID ) (* Logger , error ) {
77
90
if ! sockstats .IsAvailable {
78
91
return nil , nil
@@ -88,10 +101,9 @@ func NewLogger(logdir string, logf logger.Logf, logID logid.PublicID) (*Logger,
88
101
}
89
102
90
103
logger := & Logger {
91
- ticker : time .NewTicker (pollPeriod ),
92
- logf : logf ,
93
- filch : filch ,
94
- tr : logpolicy .NewLogtailTransport (logtail .DefaultHost ),
104
+ logf : logf ,
105
+ filch : filch ,
106
+ tr : logpolicy .NewLogtailTransport (logtail .DefaultHost ),
95
107
}
96
108
logger .logger = logtail .NewLogger (logtail.Config {
97
109
BaseURL : logpolicy .LogURL (),
@@ -124,8 +136,14 @@ func (l *Logger) SetLoggingEnabled(v bool) {
124
136
old := l .enabled .Load ()
125
137
if old != v && l .enabled .CompareAndSwap (old , v ) {
126
138
if v {
139
+ if l .eventCh == nil {
140
+ // eventCh should be large enough for the number of events that will occur within logInterval.
141
+ // Add an extra second's worth of events to ensure we don't drop any.
142
+ l .eventCh = make (chan event , (logInterval + time .Second )/ pollInterval )
143
+ }
127
144
l .ctx , l .cancelFn = context .WithCancel (context .Background ())
128
145
go l .poll ()
146
+ go l .logEvents ()
129
147
} else {
130
148
l .cancelFn ()
131
149
}
@@ -137,19 +155,21 @@ func (l *Logger) Write(p []byte) (int, error) {
137
155
}
138
156
139
157
// poll fetches the current socket stats at the configured time interval,
140
- // calculates the delta since the last poll, and logs any non-zero values.
158
+ // calculates the delta since the last poll,
159
+ // and writes any non-zero values to the logger event channel.
141
160
// This method does not return.
142
161
func (l * Logger ) poll () {
143
162
// last is the last set of socket stats we saw.
144
163
var lastStats * sockstats.SockStats
145
164
var lastTime time.Time
146
165
147
- enc := json . NewEncoder ( l )
166
+ ticker := time . NewTicker ( pollInterval )
148
167
for {
149
168
select {
150
169
case <- l .ctx .Done ():
170
+ ticker .Stop ()
151
171
return
152
- case t := <- l . ticker .C :
172
+ case t := <- ticker .C :
153
173
stats := sockstats .Get ()
154
174
if lastStats != nil {
155
175
diffstats := delta (lastStats , stats )
@@ -162,9 +182,7 @@ func (l *Logger) poll() {
162
182
if stats .CurrentInterfaceCellular {
163
183
e .IsCellularInterface = 1
164
184
}
165
- if err := enc .Encode (e ); err != nil {
166
- l .logf ("sockstatlog: error encoding log: %v" , err )
167
- }
185
+ l .eventCh <- e
168
186
}
169
187
}
170
188
lastTime = t
@@ -173,6 +191,34 @@ func (l *Logger) poll() {
173
191
}
174
192
}
175
193
194
+ // logEvents reads events from the event channel at logInterval and logs them to disk.
195
+ // This method does not return.
196
+ func (l * Logger ) logEvents () {
197
+ enc := json .NewEncoder (l )
198
+ flush := func () {
199
+ for {
200
+ select {
201
+ case e := <- l .eventCh :
202
+ if err := enc .Encode (e ); err != nil {
203
+ l .logf ("sockstatlog: error encoding log: %v" , err )
204
+ }
205
+ default :
206
+ return
207
+ }
208
+ }
209
+ }
210
+ ticker := time .NewTicker (logInterval )
211
+ for {
212
+ select {
213
+ case <- l .ctx .Done ():
214
+ ticker .Stop ()
215
+ return
216
+ case <- ticker .C :
217
+ flush ()
218
+ }
219
+ }
220
+ }
221
+
176
222
func (l * Logger ) LogID () string {
177
223
if l .logger == nil {
178
224
return ""
@@ -189,7 +235,6 @@ func (l *Logger) Shutdown() {
189
235
if l .cancelFn != nil {
190
236
l .cancelFn ()
191
237
}
192
- l .ticker .Stop ()
193
238
l .filch .Close ()
194
239
l .logger .Shutdown (context .Background ())
195
240
0 commit comments