forked from ctrlrsf/logdna
-
Notifications
You must be signed in to change notification settings - Fork 1
/
logdna.go
137 lines (111 loc) · 2.94 KB
/
logdna.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
package logdna
import (
"bytes"
"encoding/json"
"log"
"net/http"
"net/url"
"strconv"
"time"
)
// IngestBaseURL is the base URL for the LogDNA ingest API.
const IngestBaseURL = "https://logs.logdna.com/logs/ingest"
// DefaultFlushLimit is the number of log lines before we flush to LogDNA
const DefaultFlushLimit = 5000
// Config is used by NewClient to configure new clients.
type Config struct {
APIKey string
LogFile string
Hostname string
FlushLimit int
}
// Client is a client to the LogDNA logging service.
type Client struct {
config Config
payload payloadJSON
apiURL url.URL
}
// logLineJSON represents a log line in the LogDNA ingest API JSON payload.
type logLineJSON struct {
Timestamp int64 `json:"timestamp"`
Line string `json:"line"`
File string `json:"file"`
}
// payloadJSON is the complete JSON payload that will be sent to the LogDNA
// ingest API.
type payloadJSON struct {
Lines []logLineJSON `json:"lines"`
}
// makeIngestURL creats a new URL to the a full LogDNA ingest API endpoint with
// API key and requierd parameters.
func makeIngestURL(cfg Config) (url.URL, error) {
u, err := url.Parse(IngestBaseURL)
if err != nil {
return url.URL{}, err
}
u.User = url.User(cfg.APIKey)
values := url.Values{}
values.Set("hostname", cfg.Hostname)
values.Set("now", strconv.FormatInt(time.Time{}.UnixNano(), 10))
u.RawQuery = values.Encode()
return *u, err
}
// NewClient returns a Client configured to send logs to the LogDNA ingest API.
func NewClient(cfg Config) *Client {
if cfg.FlushLimit == 0 {
cfg.FlushLimit = DefaultFlushLimit
}
iu, err := makeIngestURL(cfg)
if err != nil {
log.Fatal(err)
}
client := Client{
apiURL: iu,
config: cfg,
}
return &client
}
// Log adds a new log line to Client's payload.
//
// To actually send the logs, Flush() needs to be called.
//
// Flush is called automatically if we reach the client's flush limit.
func (c *Client) Log(t time.Time, msg string) {
if c.Size() == c.config.FlushLimit {
c.Flush()
}
// Ingest API wants timestamp in milliseconds so we need to round timestamp
// down from nanoseconds.
logLine := logLineJSON{
Timestamp: t.UnixNano() / 1000000,
Line: msg,
File: c.config.LogFile,
}
c.payload.Lines = append(c.payload.Lines, logLine)
}
// Size returns the number of lines waiting to be sent.
func (c *Client) Size() int {
return len(c.payload.Lines)
}
// Flush sends any buffered logs to LogDNA and clears the buffered logs.
func (c *Client) Flush() error {
// Return immediately if no logs to send
if c.Size() == 0 {
return nil
}
jsonPayload, err := json.Marshal(c.payload)
if err != nil {
return err
}
jsonReader := bytes.NewReader(jsonPayload)
_, err = http.Post(c.apiURL.String(), "application/json", jsonReader)
if err != nil {
return err
}
c.payload = payloadJSON{}
return err
}
// Close closes the client. It also sends any buffered logs.
func (c *Client) Close() error {
return c.Flush()
}