You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

398 regels
13 KiB

  1. // Copyright 2016 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. // These features are missing now, but will likely be added:
  15. // - There is no way to specify CallOptions.
  16. // Package logadmin contains a Stackdriver Logging client that can be used
  17. // for reading logs and working with sinks, metrics and monitored resources.
  18. // For a client that can write logs, see package cloud.google.com/go/logging.
  19. //
  20. // The client uses Logging API v2.
  21. // See https://cloud.google.com/logging/docs/api/v2/ for an introduction to the API.
  22. //
  23. // Note: This package is in beta. Some backwards-incompatible changes may occur.
  24. package logadmin // import "cloud.google.com/go/logging/logadmin"
  25. import (
  26. "context"
  27. "fmt"
  28. "net/http"
  29. "net/url"
  30. "strings"
  31. "time"
  32. "cloud.google.com/go/internal/version"
  33. "cloud.google.com/go/logging"
  34. vkit "cloud.google.com/go/logging/apiv2"
  35. "cloud.google.com/go/logging/internal"
  36. "github.com/golang/protobuf/ptypes"
  37. gax "github.com/googleapis/gax-go/v2"
  38. "google.golang.org/api/iterator"
  39. "google.golang.org/api/option"
  40. _ "google.golang.org/genproto/googleapis/appengine/logging/v1" // Import the following so EntryIterator can unmarshal log protos.
  41. _ "google.golang.org/genproto/googleapis/cloud/audit"
  42. logtypepb "google.golang.org/genproto/googleapis/logging/type"
  43. logpb "google.golang.org/genproto/googleapis/logging/v2"
  44. "google.golang.org/grpc/codes"
  45. )
  46. // Client is a Logging client. A Client is associated with a single Cloud project.
  47. type Client struct {
  48. lClient *vkit.Client // logging client
  49. sClient *vkit.ConfigClient // sink client
  50. mClient *vkit.MetricsClient // metric client
  51. parent string
  52. closed bool
  53. }
  54. // NewClient returns a new logging client associated with the provided project ID.
  55. //
  56. // By default NewClient uses AdminScope. To use a different scope, call
  57. // NewClient using a WithScopes option (see https://godoc.org/google.golang.org/api/option#WithScopes).
  58. func NewClient(ctx context.Context, parent string, opts ...option.ClientOption) (*Client, error) {
  59. if !strings.ContainsRune(parent, '/') {
  60. parent = "projects/" + parent
  61. }
  62. opts = append([]option.ClientOption{
  63. option.WithEndpoint(internal.ProdAddr),
  64. option.WithScopes(logging.AdminScope),
  65. }, opts...)
  66. lc, err := vkit.NewClient(ctx, opts...)
  67. if err != nil {
  68. return nil, err
  69. }
  70. // TODO(jba): pass along any client options that should be provided to all clients.
  71. sc, err := vkit.NewConfigClient(ctx, option.WithGRPCConn(lc.Connection()))
  72. if err != nil {
  73. return nil, err
  74. }
  75. mc, err := vkit.NewMetricsClient(ctx, option.WithGRPCConn(lc.Connection()))
  76. if err != nil {
  77. return nil, err
  78. }
  79. // Retry some non-idempotent methods on INTERNAL, because it happens sometimes
  80. // and in all observed cases the operation did not complete.
  81. retryerOnInternal := func() gax.Retryer {
  82. return gax.OnCodes([]codes.Code{
  83. codes.Internal,
  84. }, gax.Backoff{
  85. Initial: 100 * time.Millisecond,
  86. Max: 1000 * time.Millisecond,
  87. Multiplier: 1.2,
  88. })
  89. }
  90. mc.CallOptions.CreateLogMetric = []gax.CallOption{gax.WithRetry(retryerOnInternal)}
  91. mc.CallOptions.UpdateLogMetric = []gax.CallOption{gax.WithRetry(retryerOnInternal)}
  92. lc.SetGoogleClientInfo("gccl", version.Repo)
  93. sc.SetGoogleClientInfo("gccl", version.Repo)
  94. mc.SetGoogleClientInfo("gccl", version.Repo)
  95. client := &Client{
  96. lClient: lc,
  97. sClient: sc,
  98. mClient: mc,
  99. parent: parent,
  100. }
  101. return client, nil
  102. }
  103. // Close closes the client.
  104. func (c *Client) Close() error {
  105. if c.closed {
  106. return nil
  107. }
  108. // Return only the first error. Since all clients share an underlying connection,
  109. // Closes after the first always report a "connection is closing" error.
  110. err := c.lClient.Close()
  111. _ = c.sClient.Close()
  112. _ = c.mClient.Close()
  113. c.closed = true
  114. return err
  115. }
  116. // DeleteLog deletes a log and all its log entries. The log will reappear if it receives new entries.
  117. // logID identifies the log within the project. An example log ID is "syslog". Requires AdminScope.
  118. func (c *Client) DeleteLog(ctx context.Context, logID string) error {
  119. return c.lClient.DeleteLog(ctx, &logpb.DeleteLogRequest{
  120. LogName: internal.LogPath(c.parent, logID),
  121. })
  122. }
  123. func toHTTPRequest(p *logtypepb.HttpRequest) (*logging.HTTPRequest, error) {
  124. if p == nil {
  125. return nil, nil
  126. }
  127. u, err := url.Parse(p.RequestUrl)
  128. if err != nil {
  129. return nil, err
  130. }
  131. var dur time.Duration
  132. if p.Latency != nil {
  133. dur, err = ptypes.Duration(p.Latency)
  134. if err != nil {
  135. return nil, err
  136. }
  137. }
  138. hr := &http.Request{
  139. Method: p.RequestMethod,
  140. URL: u,
  141. Header: map[string][]string{},
  142. }
  143. if p.UserAgent != "" {
  144. hr.Header.Set("User-Agent", p.UserAgent)
  145. }
  146. if p.Referer != "" {
  147. hr.Header.Set("Referer", p.Referer)
  148. }
  149. return &logging.HTTPRequest{
  150. Request: hr,
  151. RequestSize: p.RequestSize,
  152. Status: int(p.Status),
  153. ResponseSize: p.ResponseSize,
  154. Latency: dur,
  155. RemoteIP: p.RemoteIp,
  156. CacheHit: p.CacheHit,
  157. CacheValidatedWithOriginServer: p.CacheValidatedWithOriginServer,
  158. }, nil
  159. }
  160. // An EntriesOption is an option for listing log entries.
  161. type EntriesOption interface {
  162. set(*logpb.ListLogEntriesRequest)
  163. }
  164. // ProjectIDs sets the project IDs or project numbers from which to retrieve
  165. // log entries. Examples of a project ID: "my-project-1A", "1234567890".
  166. func ProjectIDs(pids []string) EntriesOption { return projectIDs(pids) }
  167. type projectIDs []string
  168. func (p projectIDs) set(r *logpb.ListLogEntriesRequest) {
  169. r.ResourceNames = make([]string, len(p))
  170. for i, v := range p {
  171. r.ResourceNames[i] = fmt.Sprintf("projects/%s", v)
  172. }
  173. }
  174. // ResourceNames sets the resource names from which to retrieve
  175. // log entries. Examples: "projects/my-project-1A", "organizations/my-org".
  176. func ResourceNames(rns []string) EntriesOption { return resourceNames(rns) }
  177. type resourceNames []string
  178. func (rn resourceNames) set(r *logpb.ListLogEntriesRequest) {
  179. r.ResourceNames = append([]string(nil), rn...)
  180. }
  181. // Filter sets an advanced logs filter for listing log entries (see
  182. // https://cloud.google.com/logging/docs/view/advanced_filters). The filter is
  183. // compared against all log entries in the projects specified by ProjectIDs.
  184. // Only entries that match the filter are retrieved. An empty filter (the
  185. // default) matches all log entries.
  186. //
  187. // In the filter string, log names must be written in their full form, as
  188. // "projects/PROJECT-ID/logs/LOG-ID". Forward slashes in LOG-ID must be
  189. // replaced by %2F before calling Filter.
  190. //
  191. // Timestamps in the filter string must be written in RFC 3339 format. See the
  192. // timestamp example.
  193. func Filter(f string) EntriesOption { return filter(f) }
  194. type filter string
  195. func (f filter) set(r *logpb.ListLogEntriesRequest) { r.Filter = string(f) }
  196. // NewestFirst causes log entries to be listed from most recent (newest) to
  197. // least recent (oldest). By default, they are listed from oldest to newest.
  198. func NewestFirst() EntriesOption { return newestFirst{} }
  199. type newestFirst struct{}
  200. func (newestFirst) set(r *logpb.ListLogEntriesRequest) { r.OrderBy = "timestamp desc" }
  201. // Entries returns an EntryIterator for iterating over log entries. By default,
  202. // the log entries will be restricted to those from the project passed to
  203. // NewClient. This may be overridden by passing a ProjectIDs option. Requires ReadScope or AdminScope.
  204. func (c *Client) Entries(ctx context.Context, opts ...EntriesOption) *EntryIterator {
  205. it := &EntryIterator{
  206. it: c.lClient.ListLogEntries(ctx, listLogEntriesRequest(c.parent, opts)),
  207. }
  208. it.pageInfo, it.nextFunc = iterator.NewPageInfo(
  209. it.fetch,
  210. func() int { return len(it.items) },
  211. func() interface{} { b := it.items; it.items = nil; return b })
  212. return it
  213. }
  214. func listLogEntriesRequest(parent string, opts []EntriesOption) *logpb.ListLogEntriesRequest {
  215. req := &logpb.ListLogEntriesRequest{
  216. ResourceNames: []string{parent},
  217. }
  218. for _, opt := range opts {
  219. opt.set(req)
  220. }
  221. return req
  222. }
  223. // An EntryIterator iterates over log entries.
  224. type EntryIterator struct {
  225. it *vkit.LogEntryIterator
  226. pageInfo *iterator.PageInfo
  227. nextFunc func() error
  228. items []*logging.Entry
  229. }
  230. // PageInfo supports pagination. See https://godoc.org/google.golang.org/api/iterator package for details.
  231. func (it *EntryIterator) PageInfo() *iterator.PageInfo { return it.pageInfo }
  232. // Next returns the next result. Its second return value is iterator.Done
  233. // (https://godoc.org/google.golang.org/api/iterator) if there are no more
  234. // results. Once Next returns Done, all subsequent calls will return Done.
  235. func (it *EntryIterator) Next() (*logging.Entry, error) {
  236. if err := it.nextFunc(); err != nil {
  237. return nil, err
  238. }
  239. item := it.items[0]
  240. it.items = it.items[1:]
  241. return item, nil
  242. }
  243. func (it *EntryIterator) fetch(pageSize int, pageToken string) (string, error) {
  244. return iterFetch(pageSize, pageToken, it.it.PageInfo(), func() error {
  245. item, err := it.it.Next()
  246. if err != nil {
  247. return err
  248. }
  249. e, err := fromLogEntry(item)
  250. if err != nil {
  251. return err
  252. }
  253. it.items = append(it.items, e)
  254. return nil
  255. })
  256. }
  257. var slashUnescaper = strings.NewReplacer("%2F", "/", "%2f", "/")
  258. func fromLogEntry(le *logpb.LogEntry) (*logging.Entry, error) {
  259. time, err := ptypes.Timestamp(le.Timestamp)
  260. if err != nil {
  261. return nil, err
  262. }
  263. var payload interface{}
  264. switch x := le.Payload.(type) {
  265. case *logpb.LogEntry_TextPayload:
  266. payload = x.TextPayload
  267. case *logpb.LogEntry_ProtoPayload:
  268. var d ptypes.DynamicAny
  269. if err := ptypes.UnmarshalAny(x.ProtoPayload, &d); err != nil {
  270. return nil, fmt.Errorf("logging: unmarshalling proto payload: %v", err)
  271. }
  272. payload = d.Message
  273. case *logpb.LogEntry_JsonPayload:
  274. // Leave this as a Struct.
  275. // TODO(jba): convert to map[string]interface{}?
  276. payload = x.JsonPayload
  277. default:
  278. return nil, fmt.Errorf("logging: unknown payload type: %T", le.Payload)
  279. }
  280. hr, err := toHTTPRequest(le.HttpRequest)
  281. if err != nil {
  282. return nil, err
  283. }
  284. return &logging.Entry{
  285. Timestamp: time,
  286. Severity: logging.Severity(le.Severity),
  287. Payload: payload,
  288. Labels: le.Labels,
  289. InsertID: le.InsertId,
  290. HTTPRequest: hr,
  291. Operation: le.Operation,
  292. LogName: slashUnescaper.Replace(le.LogName),
  293. Resource: le.Resource,
  294. Trace: le.Trace,
  295. SourceLocation: le.SourceLocation,
  296. }, nil
  297. }
  298. // Logs lists the logs owned by the parent resource of the client.
  299. func (c *Client) Logs(ctx context.Context) *LogIterator {
  300. it := &LogIterator{
  301. parentResource: c.parent,
  302. it: c.lClient.ListLogs(ctx, &logpb.ListLogsRequest{Parent: c.parent}),
  303. }
  304. it.pageInfo, it.nextFunc = iterator.NewPageInfo(
  305. it.fetch,
  306. func() int { return len(it.items) },
  307. func() interface{} { b := it.items; it.items = nil; return b })
  308. return it
  309. }
  310. // A LogIterator iterates over logs.
  311. type LogIterator struct {
  312. parentResource string
  313. it *vkit.StringIterator
  314. pageInfo *iterator.PageInfo
  315. nextFunc func() error
  316. items []string
  317. }
  318. // PageInfo supports pagination. See https://godoc.org/google.golang.org/api/iterator package for details.
  319. func (it *LogIterator) PageInfo() *iterator.PageInfo { return it.pageInfo }
  320. // Next returns the next result. Its second return value is iterator.Done
  321. // (https://godoc.org/google.golang.org/api/iterator) if there are no more
  322. // results. Once Next returns Done, all subsequent calls will return Done.
  323. func (it *LogIterator) Next() (string, error) {
  324. if err := it.nextFunc(); err != nil {
  325. return "", err
  326. }
  327. item := it.items[0]
  328. it.items = it.items[1:]
  329. return item, nil
  330. }
  331. func (it *LogIterator) fetch(pageSize int, pageToken string) (string, error) {
  332. return iterFetch(pageSize, pageToken, it.it.PageInfo(), func() error {
  333. logPath, err := it.it.Next()
  334. if err != nil {
  335. return err
  336. }
  337. logID := internal.LogIDFromPath(it.parentResource, logPath)
  338. it.items = append(it.items, logID)
  339. return nil
  340. })
  341. }
  342. // Common fetch code for iterators that are backed by vkit iterators.
  343. func iterFetch(pageSize int, pageToken string, pi *iterator.PageInfo, next func() error) (string, error) {
  344. pi.MaxSize = pageSize
  345. pi.Token = pageToken
  346. // Get one item, which will fill the buffer.
  347. if err := next(); err != nil {
  348. return "", err
  349. }
  350. // Collect the rest of the buffer.
  351. for pi.Remaining() > 0 {
  352. if err := next(); err != nil {
  353. return "", err
  354. }
  355. }
  356. return pi.Token, nil
  357. }