Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 

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