forked from Kong/go-plugins
-
Notifications
You must be signed in to change notification settings - Fork 0
/
go-log.go
158 lines (132 loc) · 3.38 KB
/
go-log.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
/*
A reimplementation of Kong's file-log plugin in Go.
Here we use goroutines to detach execution from the
request/response cycle.
Global variables are also long-lived, and use Mutex
locks to protect maps from being modified by
concurrent execution.
*/
package main
import (
"log"
"os"
"sync"
"github.com/Kong/go-pdk"
"github.com/Kong/go-pdk/server"
)
// Start the embedded server
// Note that the parameters are just values, there's no
// need to give them exported names as required by
// the go-pluginserver. Even the `New()` constructor
// function can be written inline. (if it's more readable
// or not is up for debate)
func main() {
server.StartServer(func() interface{} {
return &Config{}
}, "0.3", 2)
}
type Config struct {
Path string
Reopen bool
}
// Global variables have the same lifespan
// as the service process. No need
// to use any persistence to pass them
// from one event to another.
// Still, note that the service can be
// killed and respawned at any moment.
// Use some kind of external storage
// to save any state that must survive.
var fileDescriptors map[string]*os.File
var channels map[string]chan []byte
// multiple events can be handled concurrently
// global mutable maps (like those above) should
// be protected with locks
var fdmap_lock sync.Mutex
var chmap_lock sync.Mutex
func (conf Config) Log(kong *pdk.PDK) {
ch, is_new := getChannel(conf.Path)
if is_new {
// this is where it "escapes" the event cycle
go conf.collect(ch)
}
msg, err := kong.Log.Serialize()
if err != nil {
log.Print(err.Error())
return
}
ch <- []byte(msg)
}
// get the channel associated with
// the file in the config.
func getChannel(path string) (chan []byte, bool) {
chmap_lock.Lock()
defer chmap_lock.Unlock()
var is_new = false
if channels == nil {
channels = make(map[string]chan []byte)
}
ch, ok := channels[path]
if !ok {
channels[path] = make(chan []byte)
is_new = true
ch = channels[path]
}
return ch, is_new
}
//
// code below this line works "outside" Kong events and should not use the PDK
// --------------------------------
//
// Log collection
//
// Note that the goroutine that calls this function will
// persist for several Kong events so it can't use
// any PDK function. The easiest way to enforce this
// is to factor the 'long lived' code in a function
// like this and *not* pass the `kong` variable.
//
// Here the `conf` object is the one passed from
// the event that spawned the goroutine. It will
// not be updated on new events.
func (conf Config) collect(ch chan []byte) {
for {
b := <-ch
fd, ok := conf.getFileDesc()
if !ok {
return
}
fd.Write(b)
fd.Write([]byte("\n"))
}
}
// get an open descriptor for the logfile.
//
// this is called on each new event to give
// the opportunity to close and reopen if
// requested by the configuration.
func (conf Config) getFileDesc() (*os.File, bool) {
fdmap_lock.Lock()
defer fdmap_lock.Unlock()
if fileDescriptors == nil {
fileDescriptors = make(map[string]*os.File)
}
fd, ok := fileDescriptors[conf.Path]
if ok {
if conf.Reopen {
fd.Close()
delete(fileDescriptors, conf.Path)
ok = false
}
}
if !ok {
var err error
fd, err = os.OpenFile(conf.Path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
log.Printf("failed to open the file: %s", err.Error())
return nil, false
}
fileDescriptors[conf.Path] = fd
}
return fd, true
}