1 // Logger periodically writes a log to the Arvados SDK.
3 // This package is useful for maintaining a log object that is built
4 // up over time. Every time the object is modified, it will be written
5 // to the log. Writes will be throttled to no more than one every
6 // WriteFrequencySeconds
8 // This package is safe for concurrent use as long as:
9 // 1. The maps returned by Edit() are only edited in the same routine
11 // 2. Those maps not edited after calling Record()
12 // An easy way to assure this is true is to place the call to Edit()
13 // within a short block as shown below in the Usage Example:
16 // arvLogger := logger.NewLogger(params)
18 // properties, entry := arvLogger.Edit() // This will block if others are using the logger
19 // // Modifiy properties and entry however you want
20 // // properties is a shortcut for entry["properties"].(map[string]interface{})
21 // // properties can take any values you want to give it,
22 // // entry will only take the fields listed at http://doc.arvados.org/api/schema/Log.html
24 // arvLogger.Record() // This triggers the actual log write
28 "git.curoverse.com/arvados.git/sdk/go/arvadosclient"
34 type LoggerParams struct {
35 Client arvadosclient.ArvadosClient // The client we use to write log entries
36 EventType string // The event type to assign to the log entry.
37 MinimumWriteInterval time.Duration // Wait at least this long between log writes
40 // A Logger is used to build up a log entry over time and write every
44 data map[string]interface{} // The entire map that we give to the api
45 entry map[string]interface{} // Convenience shortcut into data
46 properties map[string]interface{} // Convenience shortcut into data
48 lock sync.Locker // Synchronizes editing and writing
49 params LoggerParams // Parameters we were given
51 lastWrite time.Time // The last time we wrote a log entry
52 modified bool // Has this data been modified since the last write
55 // Create a new logger based on the specified parameters.
56 func NewLogger(params LoggerParams) *Logger {
57 // TODO(misha): Add some params checking here.
58 l := &Logger{data: make(map[string]interface{}),
61 l.entry = make(map[string]interface{})
62 l.data["log"] = l.entry
63 l.properties = make(map[string]interface{})
64 l.entry["properties"] = l.properties
68 // Get access to the maps you can edit. This will hold a lock until
69 // you call Record. Do not edit the maps in any other goroutines or
70 // after calling Record.
71 // You don't need to edit both maps,
72 // properties can take any values you want to give it,
73 // entry will only take the fields listed at http://doc.arvados.org/api/schema/Log.html
74 // properties is a shortcut for entry["properties"].(map[string]interface{})
75 func (l *Logger) Edit() (properties map[string]interface{}, entry map[string]interface{}) {
77 l.modified = true // We don't actually know the caller will modifiy the data, but we assume they will.
78 return l.properties, l.entry
81 // Write the log entry you've built up so far. Do not edit the maps
82 // returned by Edit() after calling this method.
83 // If you have already written within MinimumWriteInterval, then this
84 // will schedule a future write instead.
85 // In either case, the lock will be released before Record() returns.
86 func (l *Logger) Record() {
87 if l.writeAllowedNow() {
88 // We haven't written in the allowed interval yet, try to write.
91 nextTimeToWrite := l.lastWrite.Add(l.params.MinimumWriteInterval)
92 writeAfter := nextTimeToWrite.Sub(time.Now())
93 time.AfterFunc(writeAfter, l.acquireLockConsiderWriting)
99 // Whether enough time has elapsed since the last write.
100 func (l *Logger) writeAllowedNow() bool {
101 return l.lastWrite.Add(l.params.MinimumWriteInterval).Before(time.Now())
105 // Actually writes the log entry. This method assumes we're holding the lock.
106 func (l *Logger) write() {
107 // Update the event type in case it was modified or is missing.
108 l.entry["event_type"] = l.params.EventType
109 err := l.params.Client.Create("logs", l.data, nil)
111 log.Printf("Attempted to log: %v", l.data)
112 log.Fatalf("Received error writing log: %v", err)
114 l.lastWrite = time.Now()
119 func (l *Logger) acquireLockConsiderWriting() {
121 if l.modified && l.writeAllowedNow() {
122 // We have something new to write and we're allowed to write.