1
2
3
4
5
6
7 package mongo
8
9 import (
10 "context"
11 "errors"
12 "fmt"
13 "net/http"
14 "time"
15
16 "go.mongodb.org/mongo-driver/bson"
17 "go.mongodb.org/mongo-driver/bson/bsoncodec"
18 "go.mongodb.org/mongo-driver/event"
19 "go.mongodb.org/mongo-driver/internal/httputil"
20 "go.mongodb.org/mongo-driver/internal/logger"
21 "go.mongodb.org/mongo-driver/internal/uuid"
22 "go.mongodb.org/mongo-driver/mongo/description"
23 "go.mongodb.org/mongo-driver/mongo/options"
24 "go.mongodb.org/mongo-driver/mongo/readconcern"
25 "go.mongodb.org/mongo-driver/mongo/readpref"
26 "go.mongodb.org/mongo-driver/mongo/writeconcern"
27 "go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
28 "go.mongodb.org/mongo-driver/x/mongo/driver"
29 "go.mongodb.org/mongo-driver/x/mongo/driver/mongocrypt"
30 mcopts "go.mongodb.org/mongo-driver/x/mongo/driver/mongocrypt/options"
31 "go.mongodb.org/mongo-driver/x/mongo/driver/operation"
32 "go.mongodb.org/mongo-driver/x/mongo/driver/session"
33 "go.mongodb.org/mongo-driver/x/mongo/driver/topology"
34 )
35
36 const (
37 defaultLocalThreshold = 15 * time.Millisecond
38 defaultMaxPoolSize = 100
39 )
40
41 var (
42
43 keyVaultCollOpts = options.Collection().SetReadConcern(readconcern.Majority()).
44 SetWriteConcern(writeconcern.New(writeconcern.WMajority()))
45
46 endSessionsBatchSize = 10000
47 )
48
49
50
51
52
53
54 type Client struct {
55 id uuid.UUID
56 deployment driver.Deployment
57 localThreshold time.Duration
58 retryWrites bool
59 retryReads bool
60 clock *session.ClusterClock
61 readPreference *readpref.ReadPref
62 readConcern *readconcern.ReadConcern
63 writeConcern *writeconcern.WriteConcern
64 bsonOpts *options.BSONOptions
65 registry *bsoncodec.Registry
66 monitor *event.CommandMonitor
67 serverAPI *driver.ServerAPIOptions
68 serverMonitor *event.ServerMonitor
69 sessionPool *session.Pool
70 timeout *time.Duration
71 httpClient *http.Client
72 logger *logger.Logger
73
74
75 keyVaultClientFLE *Client
76 keyVaultCollFLE *Collection
77 mongocryptdFLE *mongocryptdClient
78 cryptFLE driver.Crypt
79 metadataClientFLE *Client
80 internalClientFLE *Client
81 encryptedFieldsMap map[string]interface{}
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 func Connect(ctx context.Context, opts ...*options.ClientOptions) (*Client, error) {
107 c, err := NewClient(opts...)
108 if err != nil {
109 return nil, err
110 }
111 err = c.Connect(ctx)
112 if err != nil {
113 return nil, err
114 }
115 return c, nil
116 }
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133 func NewClient(opts ...*options.ClientOptions) (*Client, error) {
134 clientOpt := options.MergeClientOptions(opts...)
135
136 id, err := uuid.New()
137 if err != nil {
138 return nil, err
139 }
140 client := &Client{id: id}
141
142
143 client.clock = new(session.ClusterClock)
144
145
146 client.localThreshold = defaultLocalThreshold
147 if clientOpt.LocalThreshold != nil {
148 client.localThreshold = *clientOpt.LocalThreshold
149 }
150
151 if clientOpt.Monitor != nil {
152 client.monitor = clientOpt.Monitor
153 }
154
155 if clientOpt.ServerMonitor != nil {
156 client.serverMonitor = clientOpt.ServerMonitor
157 }
158
159 client.readConcern = readconcern.New()
160 if clientOpt.ReadConcern != nil {
161 client.readConcern = clientOpt.ReadConcern
162 }
163
164 client.readPreference = readpref.Primary()
165 if clientOpt.ReadPreference != nil {
166 client.readPreference = clientOpt.ReadPreference
167 }
168
169 if clientOpt.BSONOptions != nil {
170 client.bsonOpts = clientOpt.BSONOptions
171 }
172
173 client.registry = bson.DefaultRegistry
174 if clientOpt.Registry != nil {
175 client.registry = clientOpt.Registry
176 }
177
178 client.retryWrites = true
179 if clientOpt.RetryWrites != nil {
180 client.retryWrites = *clientOpt.RetryWrites
181 }
182 client.retryReads = true
183 if clientOpt.RetryReads != nil {
184 client.retryReads = *clientOpt.RetryReads
185 }
186
187 client.timeout = clientOpt.Timeout
188 client.httpClient = clientOpt.HTTPClient
189
190 if clientOpt.WriteConcern != nil {
191 client.writeConcern = clientOpt.WriteConcern
192 }
193
194 if clientOpt.AutoEncryptionOptions != nil {
195 if err := client.configureAutoEncryption(clientOpt); err != nil {
196 return nil, err
197 }
198 } else {
199 client.cryptFLE = clientOpt.Crypt
200 }
201
202
203 if clientOpt.Deployment != nil {
204 client.deployment = clientOpt.Deployment
205 }
206
207
208 if clientOpt.MaxPoolSize == nil {
209 clientOpt.SetMaxPoolSize(defaultMaxPoolSize)
210 }
211
212 if err != nil {
213 return nil, err
214 }
215
216 cfg, err := topology.NewConfig(clientOpt, client.clock)
217 if err != nil {
218 return nil, err
219 }
220 client.serverAPI = topology.ServerAPIFromServerOptions(cfg.ServerOpts)
221
222 if client.deployment == nil {
223 client.deployment, err = topology.New(cfg)
224 if err != nil {
225 return nil, replaceErrors(err)
226 }
227 }
228
229
230 client.logger, err = newLogger(clientOpt.LoggerOptions)
231 if err != nil {
232 return nil, fmt.Errorf("invalid logger options: %w", err)
233 }
234
235 return client, nil
236 }
237
238
239
240
241
242
243
244
245 func (c *Client) Connect(ctx context.Context) error {
246 if connector, ok := c.deployment.(driver.Connector); ok {
247 err := connector.Connect()
248 if err != nil {
249 return replaceErrors(err)
250 }
251 }
252
253 if c.mongocryptdFLE != nil {
254 if err := c.mongocryptdFLE.connect(ctx); err != nil {
255 return err
256 }
257 }
258
259 if c.internalClientFLE != nil {
260 if err := c.internalClientFLE.Connect(ctx); err != nil {
261 return err
262 }
263 }
264
265 if c.keyVaultClientFLE != nil && c.keyVaultClientFLE != c.internalClientFLE && c.keyVaultClientFLE != c {
266 if err := c.keyVaultClientFLE.Connect(ctx); err != nil {
267 return err
268 }
269 }
270
271 if c.metadataClientFLE != nil && c.metadataClientFLE != c.internalClientFLE && c.metadataClientFLE != c {
272 if err := c.metadataClientFLE.Connect(ctx); err != nil {
273 return err
274 }
275 }
276
277 var updateChan <-chan description.Topology
278 if subscriber, ok := c.deployment.(driver.Subscriber); ok {
279 sub, err := subscriber.Subscribe()
280 if err != nil {
281 return replaceErrors(err)
282 }
283 updateChan = sub.Updates
284 }
285 c.sessionPool = session.NewPool(updateChan)
286 return nil
287 }
288
289
290
291
292
293
294
295
296
297 func (c *Client) Disconnect(ctx context.Context) error {
298 if c.logger != nil {
299 defer c.logger.Close()
300 }
301
302 if ctx == nil {
303 ctx = context.Background()
304 }
305
306 if c.httpClient == httputil.DefaultHTTPClient {
307 defer httputil.CloseIdleHTTPConnections(c.httpClient)
308 }
309
310 c.endSessions(ctx)
311 if c.mongocryptdFLE != nil {
312 if err := c.mongocryptdFLE.disconnect(ctx); err != nil {
313 return err
314 }
315 }
316
317 if c.internalClientFLE != nil {
318 if err := c.internalClientFLE.Disconnect(ctx); err != nil {
319 return err
320 }
321 }
322
323 if c.keyVaultClientFLE != nil && c.keyVaultClientFLE != c.internalClientFLE && c.keyVaultClientFLE != c {
324 if err := c.keyVaultClientFLE.Disconnect(ctx); err != nil {
325 return err
326 }
327 }
328 if c.metadataClientFLE != nil && c.metadataClientFLE != c.internalClientFLE && c.metadataClientFLE != c {
329 if err := c.metadataClientFLE.Disconnect(ctx); err != nil {
330 return err
331 }
332 }
333 if c.cryptFLE != nil {
334 c.cryptFLE.Close()
335 }
336
337 if disconnector, ok := c.deployment.(driver.Disconnector); ok {
338 return replaceErrors(disconnector.Disconnect(ctx))
339 }
340
341 return nil
342 }
343
344
345
346
347
348
349
350
351
352
353
354
355 func (c *Client) Ping(ctx context.Context, rp *readpref.ReadPref) error {
356 if ctx == nil {
357 ctx = context.Background()
358 }
359
360 if rp == nil {
361 rp = c.readPreference
362 }
363
364 db := c.Database("admin")
365 res := db.RunCommand(ctx, bson.D{
366 {"ping", 1},
367 }, options.RunCmd().SetReadPreference(rp))
368
369 return replaceErrors(res.Err())
370 }
371
372
373
374
375
376
377
378
379
380
381
382 func (c *Client) StartSession(opts ...*options.SessionOptions) (Session, error) {
383 if c.sessionPool == nil {
384 return nil, ErrClientDisconnected
385 }
386
387 sopts := options.MergeSessionOptions(opts...)
388 coreOpts := &session.ClientOptions{
389 DefaultReadConcern: c.readConcern,
390 DefaultReadPreference: c.readPreference,
391 DefaultWriteConcern: c.writeConcern,
392 }
393 if sopts.CausalConsistency != nil {
394 coreOpts.CausalConsistency = sopts.CausalConsistency
395 }
396 if sopts.DefaultReadConcern != nil {
397 coreOpts.DefaultReadConcern = sopts.DefaultReadConcern
398 }
399 if sopts.DefaultWriteConcern != nil {
400 coreOpts.DefaultWriteConcern = sopts.DefaultWriteConcern
401 }
402 if sopts.DefaultReadPreference != nil {
403 coreOpts.DefaultReadPreference = sopts.DefaultReadPreference
404 }
405 if sopts.DefaultMaxCommitTime != nil {
406 coreOpts.DefaultMaxCommitTime = sopts.DefaultMaxCommitTime
407 }
408 if sopts.Snapshot != nil {
409 coreOpts.Snapshot = sopts.Snapshot
410 }
411
412 sess, err := session.NewClientSession(c.sessionPool, c.id, coreOpts)
413 if err != nil {
414 return nil, replaceErrors(err)
415 }
416
417
418 sess.RetryWrite = false
419 sess.RetryRead = c.retryReads
420
421 return &sessionImpl{
422 clientSession: sess,
423 client: c,
424 deployment: c.deployment,
425 }, nil
426 }
427
428 func (c *Client) endSessions(ctx context.Context) {
429 if c.sessionPool == nil {
430 return
431 }
432
433 sessionIDs := c.sessionPool.IDSlice()
434 op := operation.NewEndSessions(nil).ClusterClock(c.clock).Deployment(c.deployment).
435 ServerSelector(description.ReadPrefSelector(readpref.PrimaryPreferred())).CommandMonitor(c.monitor).
436 Database("admin").Crypt(c.cryptFLE).ServerAPI(c.serverAPI)
437
438 totalNumIDs := len(sessionIDs)
439 var currentBatch []bsoncore.Document
440 for i := 0; i < totalNumIDs; i++ {
441 currentBatch = append(currentBatch, sessionIDs[i])
442
443
444 if ((i+1)%endSessionsBatchSize) == 0 || i == totalNumIDs-1 {
445
446 _, marshalVal, err := bson.MarshalValue(currentBatch)
447 if err == nil {
448 _ = op.SessionIDs(marshalVal).Execute(ctx)
449 }
450
451 currentBatch = currentBatch[:0]
452 }
453 }
454 }
455
456 func (c *Client) configureAutoEncryption(clientOpts *options.ClientOptions) error {
457 c.encryptedFieldsMap = clientOpts.AutoEncryptionOptions.EncryptedFieldsMap
458 if err := c.configureKeyVaultClientFLE(clientOpts); err != nil {
459 return err
460 }
461 if err := c.configureMetadataClientFLE(clientOpts); err != nil {
462 return err
463 }
464
465 mc, err := c.newMongoCrypt(clientOpts.AutoEncryptionOptions)
466 if err != nil {
467 return err
468 }
469
470
471 if mc.CryptSharedLibVersionString() == "" {
472 mongocryptdFLE, err := newMongocryptdClient(clientOpts.AutoEncryptionOptions)
473 if err != nil {
474 return err
475 }
476 c.mongocryptdFLE = mongocryptdFLE
477 }
478
479 c.configureCryptFLE(mc, clientOpts.AutoEncryptionOptions)
480 return nil
481 }
482
483 func (c *Client) getOrCreateInternalClient(clientOpts *options.ClientOptions) (*Client, error) {
484 if c.internalClientFLE != nil {
485 return c.internalClientFLE, nil
486 }
487
488 internalClientOpts := options.MergeClientOptions(clientOpts)
489 internalClientOpts.AutoEncryptionOptions = nil
490 internalClientOpts.SetMinPoolSize(0)
491 var err error
492 c.internalClientFLE, err = NewClient(internalClientOpts)
493 return c.internalClientFLE, err
494 }
495
496 func (c *Client) configureKeyVaultClientFLE(clientOpts *options.ClientOptions) error {
497
498 var err error
499 aeOpts := clientOpts.AutoEncryptionOptions
500 switch {
501 case aeOpts.KeyVaultClientOptions != nil:
502 c.keyVaultClientFLE, err = NewClient(aeOpts.KeyVaultClientOptions)
503 case clientOpts.MaxPoolSize != nil && *clientOpts.MaxPoolSize == 0:
504 c.keyVaultClientFLE = c
505 default:
506 c.keyVaultClientFLE, err = c.getOrCreateInternalClient(clientOpts)
507 }
508
509 if err != nil {
510 return err
511 }
512
513 dbName, collName := splitNamespace(aeOpts.KeyVaultNamespace)
514 c.keyVaultCollFLE = c.keyVaultClientFLE.Database(dbName).Collection(collName, keyVaultCollOpts)
515 return nil
516 }
517
518 func (c *Client) configureMetadataClientFLE(clientOpts *options.ClientOptions) error {
519
520 aeOpts := clientOpts.AutoEncryptionOptions
521 if aeOpts.BypassAutoEncryption != nil && *aeOpts.BypassAutoEncryption {
522
523 return nil
524 }
525 if clientOpts.MaxPoolSize != nil && *clientOpts.MaxPoolSize == 0 {
526 c.metadataClientFLE = c
527 return nil
528 }
529
530 var err error
531 c.metadataClientFLE, err = c.getOrCreateInternalClient(clientOpts)
532 return err
533 }
534
535 func (c *Client) newMongoCrypt(opts *options.AutoEncryptionOptions) (*mongocrypt.MongoCrypt, error) {
536
537 cryptSchemaMap := make(map[string]bsoncore.Document)
538 for k, v := range opts.SchemaMap {
539 schema, err := marshal(v, c.bsonOpts, c.registry)
540 if err != nil {
541 return nil, err
542 }
543 cryptSchemaMap[k] = schema
544 }
545
546
547 cryptEncryptedFieldsMap := make(map[string]bsoncore.Document)
548 for k, v := range opts.EncryptedFieldsMap {
549 encryptedFields, err := marshal(v, c.bsonOpts, c.registry)
550 if err != nil {
551 return nil, err
552 }
553 cryptEncryptedFieldsMap[k] = encryptedFields
554 }
555
556 kmsProviders, err := marshal(opts.KmsProviders, c.bsonOpts, c.registry)
557 if err != nil {
558 return nil, fmt.Errorf("error creating KMS providers document: %w", err)
559 }
560
561
562
563 cryptSharedLibPath := ""
564 if val, ok := opts.ExtraOptions["cryptSharedLibPath"]; ok {
565 str, ok := val.(string)
566 if !ok {
567 return nil, fmt.Errorf(
568 `expected AutoEncryption extra option "cryptSharedLibPath" to be a string, but is a %T`, val)
569 }
570 cryptSharedLibPath = str
571 }
572
573
574
575
576 cryptSharedLibDisabled := false
577 if v, ok := opts.ExtraOptions["__cryptSharedLibDisabledForTestOnly"]; ok {
578 cryptSharedLibDisabled = v.(bool)
579 }
580
581 bypassAutoEncryption := opts.BypassAutoEncryption != nil && *opts.BypassAutoEncryption
582 bypassQueryAnalysis := opts.BypassQueryAnalysis != nil && *opts.BypassQueryAnalysis
583
584 mc, err := mongocrypt.NewMongoCrypt(mcopts.MongoCrypt().
585 SetKmsProviders(kmsProviders).
586 SetLocalSchemaMap(cryptSchemaMap).
587 SetBypassQueryAnalysis(bypassQueryAnalysis).
588 SetEncryptedFieldsMap(cryptEncryptedFieldsMap).
589 SetCryptSharedLibDisabled(cryptSharedLibDisabled || bypassAutoEncryption).
590 SetCryptSharedLibOverridePath(cryptSharedLibPath).
591 SetHTTPClient(opts.HTTPClient))
592 if err != nil {
593 return nil, err
594 }
595
596 var cryptSharedLibRequired bool
597 if val, ok := opts.ExtraOptions["cryptSharedLibRequired"]; ok {
598 b, ok := val.(bool)
599 if !ok {
600 return nil, fmt.Errorf(
601 `expected AutoEncryption extra option "cryptSharedLibRequired" to be a bool, but is a %T`, val)
602 }
603 cryptSharedLibRequired = b
604 }
605
606
607
608
609 if cryptSharedLibRequired && mc.CryptSharedLibVersionString() == "" {
610 return nil, errors.New(
611 `AutoEncryption extra option "cryptSharedLibRequired" is true, but we failed to load the crypt_shared library`)
612 }
613
614 return mc, nil
615 }
616
617
618 func (c *Client) configureCryptFLE(mc *mongocrypt.MongoCrypt, opts *options.AutoEncryptionOptions) {
619 bypass := opts.BypassAutoEncryption != nil && *opts.BypassAutoEncryption
620 kr := keyRetriever{coll: c.keyVaultCollFLE}
621 var cir collInfoRetriever
622
623
624
625 if !bypass {
626 cir = collInfoRetriever{client: c.metadataClientFLE}
627 }
628
629 c.cryptFLE = driver.NewCrypt(&driver.CryptOptions{
630 MongoCrypt: mc,
631 CollInfoFn: cir.cryptCollInfo,
632 KeyFn: kr.cryptKeys,
633 MarkFn: c.mongocryptdFLE.markCommand,
634 TLSConfig: opts.TLSConfig,
635 BypassAutoEncryption: bypass,
636 })
637 }
638
639
640 func (c *Client) validSession(sess *session.Client) error {
641 if sess != nil && sess.ClientID != c.id {
642 return ErrWrongClient
643 }
644 return nil
645 }
646
647
648 func (c *Client) Database(name string, opts ...*options.DatabaseOptions) *Database {
649 return newDatabase(c, name, opts...)
650 }
651
652
653
654
655
656
657
658
659
660
661 func (c *Client) ListDatabases(ctx context.Context, filter interface{}, opts ...*options.ListDatabasesOptions) (ListDatabasesResult, error) {
662 if ctx == nil {
663 ctx = context.Background()
664 }
665
666 sess := sessionFromContext(ctx)
667
668 err := c.validSession(sess)
669 if err != nil {
670 return ListDatabasesResult{}, err
671 }
672 if sess == nil && c.sessionPool != nil {
673 sess = session.NewImplicitClientSession(c.sessionPool, c.id)
674 defer sess.EndSession()
675 }
676
677 err = c.validSession(sess)
678 if err != nil {
679 return ListDatabasesResult{}, err
680 }
681
682 filterDoc, err := marshal(filter, c.bsonOpts, c.registry)
683 if err != nil {
684 return ListDatabasesResult{}, err
685 }
686
687 selector := description.CompositeSelector([]description.ServerSelector{
688 description.ReadPrefSelector(readpref.Primary()),
689 description.LatencySelector(c.localThreshold),
690 })
691 selector = makeReadPrefSelector(sess, selector, c.localThreshold)
692
693 ldo := options.MergeListDatabasesOptions(opts...)
694 op := operation.NewListDatabases(filterDoc).
695 Session(sess).ReadPreference(c.readPreference).CommandMonitor(c.monitor).
696 ServerSelector(selector).ClusterClock(c.clock).Database("admin").Deployment(c.deployment).Crypt(c.cryptFLE).
697 ServerAPI(c.serverAPI).Timeout(c.timeout)
698
699 if ldo.NameOnly != nil {
700 op = op.NameOnly(*ldo.NameOnly)
701 }
702 if ldo.AuthorizedDatabases != nil {
703 op = op.AuthorizedDatabases(*ldo.AuthorizedDatabases)
704 }
705
706 retry := driver.RetryNone
707 if c.retryReads {
708 retry = driver.RetryOncePerCommand
709 }
710 op.Retry(retry)
711
712 err = op.Execute(ctx)
713 if err != nil {
714 return ListDatabasesResult{}, replaceErrors(err)
715 }
716
717 return newListDatabasesResultFromOperation(op.Result()), nil
718 }
719
720
721
722
723
724
725
726
727
728
729
730
731 func (c *Client) ListDatabaseNames(ctx context.Context, filter interface{}, opts ...*options.ListDatabasesOptions) ([]string, error) {
732 opts = append(opts, options.ListDatabases().SetNameOnly(true))
733
734 res, err := c.ListDatabases(ctx, filter, opts...)
735 if err != nil {
736 return nil, err
737 }
738
739 names := make([]string, 0)
740 for _, spec := range res.Databases {
741 names = append(names, spec.Name)
742 }
743
744 return names, nil
745 }
746
747
748
749
750
751
752
753
754
755
756
757 func WithSession(ctx context.Context, sess Session, fn func(SessionContext) error) error {
758 return fn(NewSessionContext(ctx, sess))
759 }
760
761
762
763
764
765
766
767
768
769
770
771
772 func (c *Client) UseSession(ctx context.Context, fn func(SessionContext) error) error {
773 return c.UseSessionWithOptions(ctx, options.Session(), fn)
774 }
775
776
777
778
779
780 func (c *Client) UseSessionWithOptions(ctx context.Context, opts *options.SessionOptions, fn func(SessionContext) error) error {
781 defaultSess, err := c.StartSession(opts)
782 if err != nil {
783 return err
784 }
785
786 defer defaultSess.EndSession(ctx)
787 return fn(NewSessionContext(ctx, defaultSess))
788 }
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803 func (c *Client) Watch(ctx context.Context, pipeline interface{},
804 opts ...*options.ChangeStreamOptions) (*ChangeStream, error) {
805 if c.sessionPool == nil {
806 return nil, ErrClientDisconnected
807 }
808
809 csConfig := changeStreamConfig{
810 readConcern: c.readConcern,
811 readPreference: c.readPreference,
812 client: c,
813 bsonOpts: c.bsonOpts,
814 registry: c.registry,
815 streamType: ClientStream,
816 crypt: c.cryptFLE,
817 }
818
819 return newChangeStream(ctx, csConfig, pipeline, opts...)
820 }
821
822
823
824 func (c *Client) NumberSessionsInProgress() int {
825
826
827
828 return int(c.sessionPool.CheckedOut())
829 }
830
831
832 func (c *Client) Timeout() *time.Duration {
833 return c.timeout
834 }
835
836 func (c *Client) createBaseCursorOptions() driver.CursorOptions {
837 return driver.CursorOptions{
838 CommandMonitor: c.monitor,
839 Crypt: c.cryptFLE,
840 ServerAPI: c.serverAPI,
841 }
842 }
843
844
845
846 func newLogger(opts *options.LoggerOptions) (*logger.Logger, error) {
847
848 if opts == nil {
849 opts = options.Logger()
850 }
851
852
853
854 if (opts.ComponentLevels == nil || len(opts.ComponentLevels) == 0) &&
855 !logger.EnvHasComponentVariables() {
856
857 return nil, nil
858 }
859
860
861 componentLevels := make(map[logger.Component]logger.Level)
862 for component, level := range opts.ComponentLevels {
863 componentLevels[logger.Component(component)] = logger.Level(level)
864 }
865
866 return logger.New(opts.Sink, opts.MaxDocumentLength, componentLevels)
867 }
868
View as plain text