Repository: open-telemetry/opamp-go
Pull request #237 (2024-01-10 22:52:15)

Add context and info to logging interface
Allow the logging interface to propagate context where possible. This will let logging sinks contain richer information about potential opamp IO errors.

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/open-telemetry/opamp-go/pull/237?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) Report
Attention: `153 lines` in your changes are missing coverage. Please review.
> Comparison is base [(`efddaa2`)](https://app.codecov.io/gh/open-telemetry/opamp-go/commit/efddaa260895e7ebb2431deeefef3a3380d8d902?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) 76.11% compared to head [(`7971f49`)](https://app.codecov.io/gh/open-telemetry/opamp-go/pull/237?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) 71.71%.
> Report is 42 commits behind head on main.

| [Files](https://app.codecov.io/gh/open-telemetry/opamp-go/pull/237?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) | Patch % | Lines |
|---|---|---|
| [internal/certs.go](https://app.codecov.io/gh/open-telemetry/opamp-go/pull/237?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry#diff-aW50ZXJuYWwvY2VydHMuZ28=) | 0.00% | [118 Missing :warning: ](https://app.codecov.io/gh/open-telemetry/opamp-go/pull/237?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) |
| [client/internal/httpsender.go](https://app.codecov.io/gh/open-telemetry/opamp-go/pull/237?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry#diff-Y2xpZW50L2ludGVybmFsL2h0dHBzZW5kZXIuZ28=) | 72.72% | [8 Missing and 1 partial :warning: ](https://app.codecov.io/gh/open-telemetry/opamp-go/pull/237?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) |
| [client/internal/receivedprocessor.go](https://app.codecov.io/gh/open-telemetry/opamp-go/pull/237?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry#diff-Y2xpZW50L2ludGVybmFsL3JlY2VpdmVkcHJvY2Vzc29yLmdv) | 55.00% | [9 Missing :warning: ](https://app.codecov.io/gh/open-telemetry/opamp-go/pull/237?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) |
| [client/internal/packagessyncer.go](https://app.codecov.io/gh/open-telemetry/opamp-go/pull/237?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry#diff-Y2xpZW50L2ludGVybmFsL3BhY2thZ2Vzc3luY2VyLmdv) | 40.00% | [6 Missing :warning: ](https://app.codecov.io/gh/open-telemetry/opamp-go/pull/237?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) |
| [client/internal/mockserver.go](https://app.codecov.io/gh/open-telemetry/opamp-go/pull/237?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry#diff-Y2xpZW50L2ludGVybmFsL21vY2tzZXJ2ZXIuZ28=) | 85.71% | [2 Missing and 1 partial :warning: ](https://app.codecov.io/gh/open-telemetry/opamp-go/pull/237?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) |
| [client/wsclient.go](https://app.codecov.io/gh/open-telemetry/opamp-go/pull/237?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry#diff-Y2xpZW50L3dzY2xpZW50Lmdv) | 75.00% | [2 Missing :warning: ](https://app.codecov.io/gh/open-telemetry/opamp-go/pull/237?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) |
| [internal/noplogger.go](https://app.codecov.io/gh/open-telemetry/opamp-go/pull/237?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry#diff-aW50ZXJuYWwvbm9wbG9nZ2VyLmdv) | 66.66% | [2 Missing :warning: ](https://app.codecov.io/gh/open-telemetry/opamp-go/pull/237?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) |
| [server/httpconnection.go](https://app.codecov.io/gh/open-telemetry/opamp-go/pull/237?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry#diff-c2VydmVyL2h0dHBjb25uZWN0aW9uLmdv) | 0.00% | [2 Missing :warning: ](https://app.codecov.io/gh/open-telemetry/opamp-go/pull/237?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) |
| [client/internal/clientcommon.go](https://app.codecov.io/gh/open-telemetry/opamp-go/pull/237?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry#diff-Y2xpZW50L2ludGVybmFsL2NsaWVudGNvbW1vbi5nbw==) | 90.90% | [1 Missing :warning: ](https://app.codecov.io/gh/open-telemetry/opamp-go/pull/237?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) |
| [server/serverimpl.go](https://app.codecov.io/gh/open-telemetry/opamp-go/pull/237?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry#diff-c2VydmVyL3NlcnZlcmltcGwuZ28=) | 95.65% | [0 Missing and 1 partial :warning: ](https://app.codecov.io/gh/open-telemetry/opamp-go/pull/237?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main     #237      +/-   ##
==========================================
- Coverage   76.11%   71.71%   -4.41%     
==========================================
  Files          24       25       +1     
  Lines        1834     2026     +192     
==========================================
+ Hits         1396     1453      +57     
- Misses        326      458     +132     
- Partials      112      115       +3     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/open-telemetry/opamp-go/pull/237?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry).

jaronoff97: Before i continue with this... @andykellr @evan-bradley @tigrannajaryan what do you all think of this approach? Propagating the context would make it easier for consumers to understand the potential problems that the impls run in to, but i'm not sure how i feel about the interface. Let me know your thoughts. Thank you.
tigrannajaryan: I have a hard time reviewing the code due to all the junk codecov added. I am going to disable it for now.
tigrannajaryan: The idea to pass the context to logging functions sounds good to me. I am not sure we need a separate set of functions for that. Perhaps just add context parameter to existing functions?
jaronoff97: @tigrannajaryan sounds good! I'll update to do that.
mwear: One benefit to the current approach is that the interface matches the new Go structured logger, [slog](https://pkg.go.dev/log/slog#Logger). Rather than adding context to the existing functions, it might make more sense to keep the context versions, and remove (or deprecate) the non-context versions. That way you can use any logger that is `slog` compatible.
jaronoff97: yeah that was the intended purpose... I see benefits for both approaches... Maybe we should discuss at the next SIG call?
tigrannajaryan: The `Logger` interface here is not a general-purpose logging API. It is on purpose minimalistic and designed to support only what is needed by opamp implementation and nothing else. If we want to pass the context I am happy to make it a required parameter, I don't see the need to give a choice (side note: I argued that slog also makes it a required parameter, but wasn't able to convince the author).

I prefer to keep Logger interface as small as possible and bridge to any other logging API as needed. Coding a bridge from `Logger` to `slog` is so easy that I don't see the need to make the interface identical.

We also do not have stability promises in this repo yet, so I am not too worried about making a breaking change that is very easy to adopt. I don't think we need a deprecations process for this.


jaronoff97: done! I will also work on the follow up described [here](#214) which will make these even more valuable.

Patch:
diff --git a/client/internal/httpsender.go b/client/internal/httpsender.go
index 21f475e..f5a5027 100644
--- a/client/internal/httpsender.go
+++ b/client/internal/httpsender.go
@@ -134,7 +134,7 @@ func (h *HTTPSender) SetRequestHeader(header http.Header) {
 func (h *HTTPSender) makeOneRequestRoundtrip(ctx context.Context) {
 	resp, err := h.sendRequestWithRetries(ctx)
 	if err != nil {
-		h.logger.Errorf("%v", err)
+		h.logger.Errorf(ctx, "%v", err)
 		return
 	}
 	if resp == nil {
@@ -148,9 +148,9 @@ func (h *HTTPSender) sendRequestWithRetries(ctx context.Context) (*http.Response
 	req, err := h.prepareRequest(ctx)
 	if err != nil {
 		if errors.Is(err, context.Canceled) {
-			h.logger.Debugf("Client is stopped, will not try anymore.")
+			h.logger.Debugf(ctx, "Client is stopped, will not try anymore.")
 		} else {
-			h.logger.Errorf("Failed prepare request (%v), will not try anymore.", err)
+			h.logger.Errorf(ctx, "Failed prepare request (%v), will not try anymore.", err)
 		}
 		return nil, err
 	}
@@ -190,16 +190,16 @@ func (h *HTTPSender) sendRequestWithRetries(ctx context.Context) (*http.Response
 						return nil, fmt.Errorf("invalid response from server: %d", resp.StatusCode)
 					}
 				} else if errors.Is(err, context.Canceled) {
-					h.logger.Debugf("Client is stopped, will not try anymore.")
+					h.logger.Debugf(ctx, "Client is stopped, will not try anymore.")
 					return nil, err
 				}
 
-				h.logger.Errorf("Failed to do HTTP request (%v), will retry", err)
+				h.logger.Errorf(ctx, "Failed to do HTTP request (%v), will retry", err)
 				h.callbacks.OnConnectFailed(err)
 			}
 
 		case <-ctx.Done():
-			h.logger.Debugf("Client is stopped, will not try anymore.")
+			h.logger.Debugf(ctx, "Client is stopped, will not try anymore.")
 			return nil, ctx.Err()
 		}
 	}
@@ -239,11 +239,11 @@ func (h *HTTPSender) prepareRequest(ctx context.Context) (*requestWrapper, error
 		var buf bytes.Buffer
 		g := gzip.NewWriter(&buf)
 		if _, err = g.Write(data); err != nil {
-			h.logger.Errorf("Failed to compress message: %v", err)
+			h.logger.Errorf(ctx, "Failed to compress message: %v", err)
 			return nil, err
 		}
 		if err = g.Close(); err != nil {
-			h.logger.Errorf("Failed to close the writer: %v", err)
+			h.logger.Errorf(ctx, "Failed to close the writer: %v", err)
 			return nil, err
 		}
 		req.bodyReader = bodyReader(buf.Bytes())
@@ -262,14 +262,14 @@ func (h *HTTPSender) receiveResponse(ctx context.Context, resp *http.Response) {
 	msgBytes, err := io.ReadAll(resp.Body)
 	if err != nil {
 		_ = resp.Body.Close()
-		h.logger.Errorf("cannot read response body: %v", err)
+		h.logger.Errorf(ctx, "cannot read response body: %v", err)
 		return
 	}
 	_ = resp.Body.Close()
 
 	var response protobufs.ServerToAgent
 	if err := proto.Unmarshal(msgBytes, &response); err != nil {
-		h.logger.Errorf("cannot unmarshal response: %v", err)
+		h.logger.Errorf(ctx, "cannot unmarshal response: %v", err)
 		return
 	}
 
diff --git a/client/internal/packagessyncer.go b/client/internal/packagessyncer.go
index 4c13308..f681440 100644
--- a/client/internal/packagessyncer.go
+++ b/client/internal/packagessyncer.go
@@ -101,17 +101,17 @@ func (s *packagesSyncer) initStatuses() error {
 func (s *packagesSyncer) doSync(ctx context.Context) {
 	hash, err := s.localState.AllPackagesHash()
 	if err != nil {
-		s.logger.Errorf("Package syncing failed: %V", err)
+		s.logger.Errorf(ctx, "Package syncing failed: %V", err)
 		return
 	}
 	if bytes.Compare(hash, s.available.AllPackagesHash) == 0 {
-		s.logger.Debugf("All packages are already up to date.")
+		s.logger.Debugf(ctx, "All packages are already up to date.")
 		return
 	}
 
 	failed := false
-	if err := s.deleteUnneededLocalPackages(); err != nil {
-		s.logger.Errorf("Cannot delete unneeded packages: %v", err)
+	if err := s.deleteUnneededLocalPackages(ctx); err != nil {
+		s.logger.Errorf(ctx, "Cannot delete unneeded packages: %v", err)
 		failed = true
 	}
 
@@ -119,7 +119,7 @@ func (s *packagesSyncer) doSync(ctx context.Context) {
 	for name, pkg := range s.available.Packages {
 		err := s.syncPackage(ctx, name, pkg)
 		if err != nil {
-			s.logger.Errorf("Cannot sync package %s: %v", name, err)
+			s.logger.Errorf(ctx, "Cannot sync package %s: %v", name, err)
 			failed = true
 		}
 	}
@@ -128,15 +128,15 @@ func (s *packagesSyncer) doSync(ctx context.Context) {
 		// Update the "all" hash on success, so that next time Sync() does not thing,
 		// unless a new hash is received from the Server.
 		if err := s.localState.SetAllPackagesHash(s.available.AllPackagesHash); err != nil {
-			s.logger.Errorf("SetAllPackagesHash failed: %v", err)
+			s.logger.Errorf(ctx, "SetAllPackagesHash failed: %v", err)
 		} else {
-			s.logger.Debugf("All packages are synced and up to date.")
+			s.logger.Debugf(ctx, "All packages are synced and up to date.")
 		}
 	} else {
-		s.logger.Errorf("Package syncing was not successful.")
+		s.logger.Errorf(ctx, "Package syncing was not successful.")
 	}
 
-	_ = s.reportStatuses(true)
+	_ = s.reportStatuses(ctx, true)
 }
 
 // syncPackage downloads the package from the server and installs it.
@@ -165,7 +165,7 @@ func (s *packagesSyncer) syncPackage(
 	mustCreate := !pkgLocal.Exists
 	if pkgLocal.Exists {
 		if bytes.Equal(pkgLocal.Hash, pkgAvail.Hash) {
-			s.logger.Debugf("Package %s hash is unchanged, skipping", pkgName)
+			s.logger.Debugf(ctx, "Package %s hash is unchanged, skipping", pkgName)
 			return nil
 		}
 		if pkgLocal.Type != pkgAvail.Type {
@@ -183,7 +183,7 @@ func (s *packagesSyncer) syncPackage(
 
 	// Report that we are beginning to install it.
 	status.Status = protobufs.PackageStatusEnum_PackageStatusEnum_Installing
-	_ = s.reportStatuses(true)
+	_ = s.reportStatuses(ctx, true)
 
 	if mustCreate {
 		// Make sure the package exists.
@@ -213,7 +213,7 @@ func (s *packagesSyncer) syncPackage(
 		status.Status = protobufs.PackageStatusEnum_PackageStatusEnum_InstallFailed
 		status.ErrorMessage = err.Error()
 	}
-	_ = s.reportStatuses(true)
+	_ = s.reportStatuses(ctx, true)
 
 	return err
 }
@@ -224,7 +224,7 @@ func (s *packagesSyncer) syncPackage(
 func (s *packagesSyncer) syncPackageFile(
 	ctx context.Context, pkgName string, file *protobufs.DownloadableFile,
 ) error {
-	shouldDownload, err := s.shouldDownloadFile(pkgName, file)
+	shouldDownload, err := s.shouldDownloadFile(ctx, pkgName, file)
 	if err == nil && shouldDownload {
 		err = s.downloadFile(ctx, pkgName, file)
 	}
@@ -233,21 +233,18 @@ func (s *packagesSyncer) syncPackageFile(
 }
 
 // shouldDownloadFile returns true if the file should be downloaded.
-func (s *packagesSyncer) shouldDownloadFile(
-	packageName string,
-	file *protobufs.DownloadableFile,
-) (bool, error) {
+func (s *packagesSyncer) shouldDownloadFile(ctx context.Context, packageName string, file *protobufs.DownloadableFile) (bool, error) {
 	fileContentHash, err := s.localState.FileContentHash(packageName)
 
 	if err != nil {
 		err := fmt.Errorf("cannot calculate checksum of %s: %v", packageName, err)
-		s.logger.Errorf(err.Error())
+		s.logger.Errorf(ctx, err.Error())
 		return true, nil
 	} else {
 		// Compare the checksum of the file we have with what
 		// we are offered by the server.
 		if bytes.Compare(fileContentHash, file.ContentHash) != 0 {
-			s.logger.Debugf("Package %s: file hash mismatch, will download.", packageName)
+			s.logger.Debugf(ctx, "Package %s: file hash mismatch, will download.", packageName)
 			return true, nil
 		}
 	}
@@ -256,7 +253,7 @@ func (s *packagesSyncer) shouldDownloadFile(
 
 // downloadFile downloads the file from the server.
 func (s *packagesSyncer) downloadFile(ctx context.Context, pkgName string, file *protobufs.DownloadableFile) error {
-	s.logger.Debugf("Downloading package %s file from %s", pkgName, file.DownloadUrl)
+	s.logger.Debugf(ctx, "Downloading package %s file from %s", pkgName, file.DownloadUrl)
 
 	req, err := http.NewRequestWithContext(ctx, "GET", file.DownloadUrl, nil)
 	if err != nil {
@@ -286,7 +283,7 @@ func (s *packagesSyncer) downloadFile(ctx context.Context, pkgName string, file
 // deleteUnneededLocalPackages deletes local packages that are not
 // needed anymore. This is done by comparing the local package state
 // with the server's package state.
-func (s *packagesSyncer) deleteUnneededLocalPackages() error {
+func (s *packagesSyncer) deleteUnneededLocalPackages(ctx context.Context) error {
 	// Read the list of packages we have locally.
 	localPackages, err := s.localState.Packages()
 	if err != nil {
@@ -297,7 +294,7 @@ func (s *packagesSyncer) deleteUnneededLocalPackages() error {
 	for _, localPkg := range localPackages {
 		// Do we have a package that is not offered?
 		if _, offered := s.available.Packages[localPkg]; !offered {
-			s.logger.Debugf("Package %s is no longer needed, deleting.", localPkg)
+			s.logger.Debugf(ctx, "Package %s is no longer needed, deleting.", localPkg)
 			err := s.localState.DeletePackage(localPkg)
 			if err != nil {
 				lastErr = err
@@ -318,16 +315,16 @@ func (s *packagesSyncer) deleteUnneededLocalPackages() error {
 // reportStatuses saves the last reported statuses to provider and client state.
 // If sendImmediately is true, the statuses are scheduled to be
 // sent to the server.
-func (s *packagesSyncer) reportStatuses(sendImmediately bool) error {
+func (s *packagesSyncer) reportStatuses(ctx context.Context, sendImmediately bool) error {
 	// Save it in the user-supplied state provider.
 	if err := s.localState.SetLastReportedStatuses(s.statuses); err != nil {
-		s.logger.Errorf("Cannot save last reported statuses: %v", err)
+		s.logger.Errorf(ctx, "Cannot save last reported statuses: %v", err)
 		return err
 	}
 
 	// Also save it in our internal state (will be needed if the Server asks for it).
 	if err := s.clientSyncedState.SetPackageStatuses(s.statuses); err != nil {
-		s.logger.Errorf("Cannot save client state: %v", err)
+		s.logger.Errorf(ctx, "Cannot save client state: %v", err)
 		return err
 	}
 	s.sender.NextMessage().Update(
diff --git a/client/internal/receivedprocessor.go b/client/internal/receivedprocessor.go
index df80b1d..a7daa69 100644
--- a/client/internal/receivedprocessor.go
+++ b/client/internal/receivedprocessor.go
@@ -60,13 +60,13 @@ func (r *receivedProcessor) ProcessReceivedMessage(ctx context.Context, msg *pro
 				// If a command message exists, other messages will be ignored
 				return
 			} else {
-				r.logger.Debugf("Ignoring Command, agent does not have AcceptsCommands capability")
+				r.logger.Debugf(ctx, "Ignoring Command, agent does not have AcceptsCommands capability")
 			}
 		}
 
 		scheduled, err := r.rcvFlags(ctx, protobufs.ServerToAgentFlags(msg.Flags))
 		if err != nil {
-			r.logger.Errorf("cannot processed received flags:%v", err)
+			r.logger.Errorf(ctx, "cannot processed received flags:%v", err)
 		}
 
 		msgData := &types.MessageData{}
@@ -75,7 +75,7 @@ func (r *receivedProcessor) ProcessReceivedMessage(ctx context.Context, msg *pro
 			if r.hasCapability(protobufs.AgentCapabilities_AgentCapabilities_AcceptsRemoteConfig) {
 				msgData.RemoteConfig = msg.RemoteConfig
 			} else {
-				r.logger.Debugf("Ignoring RemoteConfig, agent does not have AcceptsRemoteConfig capability")
+				r.logger.Debugf(ctx, "Ignoring RemoteConfig, agent does not have AcceptsRemoteConfig capability")
 			}
 		}
 
@@ -84,7 +84,7 @@ func (r *receivedProcessor) ProcessReceivedMessage(ctx context.Context, msg *pro
 				if r.hasCapability(protobufs.AgentCapabilities_AgentCapabilities_ReportsOwnMetrics) {
 					msgData.OwnMetricsConnSettings = msg.ConnectionSettings.OwnMetrics
 				} else {
-					r.logger.Debugf("Ignoring OwnMetrics, agent does not have ReportsOwnMetrics capability")
+					r.logger.Debugf(ctx, "Ignoring OwnMetrics, agent does not have ReportsOwnMetrics capability")
 				}
 			}
 
@@ -92,7 +92,7 @@ func (r *receivedProcessor) ProcessReceivedMessage(ctx context.Context, msg *pro
 				if r.hasCapability(protobufs.AgentCapabilities_AgentCapabilities_ReportsOwnTraces) {
 					msgData.OwnTracesConnSettings = msg.ConnectionSettings.OwnTraces
 				} else {
-					r.logger.Debugf("Ignoring OwnTraces, agent does not have ReportsOwnTraces capability")
+					r.logger.Debugf(ctx, "Ignoring OwnTraces, agent does not have ReportsOwnTraces capability")
 				}
 			}
 
@@ -100,7 +100,7 @@ func (r *receivedProcessor) ProcessReceivedMessage(ctx context.Context, msg *pro
 				if r.hasCapability(protobufs.AgentCapabilities_AgentCapabilities_ReportsOwnLogs) {
 					msgData.OwnLogsConnSettings = msg.ConnectionSettings.OwnLogs
 				} else {
-					r.logger.Debugf("Ignoring OwnLogs, agent does not have ReportsOwnLogs capability")
+					r.logger.Debugf(ctx, "Ignoring OwnLogs, agent does not have ReportsOwnLogs capability")
 				}
 			}
 
@@ -108,7 +108,7 @@ func (r *receivedProcessor) ProcessReceivedMessage(ctx context.Context, msg *pro
 				if r.hasCapability(protobufs.AgentCapabilities_AgentCapabilities_AcceptsOtherConnectionSettings) {
 					msgData.OtherConnSettings = msg.ConnectionSettings.OtherConnections
 				} else {
-					r.logger.Debugf("Ignoring OtherConnections, agent does not have AcceptsOtherConnectionSettings capability")
+					r.logger.Debugf(ctx, "Ignoring OtherConnections, agent does not have AcceptsOtherConnectionSettings capability")
 				}
 			}
 		}
@@ -124,7 +124,7 @@ func (r *receivedProcessor) ProcessReceivedMessage(ctx context.Context, msg *pro
 					r.packagesStateProvider,
 				)
 			} else {
-				r.logger.Debugf("Ignoring PackagesAvailable, agent does not have AcceptsPackages capability")
+				r.logger.Debugf(ctx, "Ignoring PackagesAvailable, agent does not have AcceptsPackages capability")
 			}
 		}
 
@@ -164,7 +164,7 @@ func (r *receivedProcessor) rcvFlags(
 	if flags&protobufs.ServerToAgentFlags_ServerToAgentFlags_ReportFullState != 0 {
 		cfg, err := r.callbacks.GetEffectiveConfig(ctx)
 		if err != nil {
-			r.logger.Errorf("Cannot GetEffectiveConfig: %v", err)
+			r.logger.Errorf(ctx, "Cannot GetEffectiveConfig: %v", err)
 			cfg = nil
 		}
 
@@ -199,25 +199,25 @@ func (r *receivedProcessor) rcvOpampConnectionSettings(ctx context.Context, sett
 			r.callbacks.OnOpampConnectionSettingsAccepted(settings.Opamp)
 		}
 	} else {
-		r.logger.Debugf("Ignoring Opamp, agent does not have AcceptsOpAMPConnectionSettings capability")
+		r.logger.Debugf(ctx, "Ignoring Opamp, agent does not have AcceptsOpAMPConnectionSettings capability")
 	}
 }
 
 func (r *receivedProcessor) processErrorResponse(body *protobufs.ServerErrorResponse) {
 	// TODO: implement this.
-	r.logger.Errorf("received an error from server: %s", body.ErrorMessage)
+	r.logger.Errorf(context.Background(), "received an error from server: %s", body.ErrorMessage)
 }
 
 func (r *receivedProcessor) rcvAgentIdentification(agentId *protobufs.AgentIdentification) error {
 	if agentId.NewInstanceUid == "" {
 		err := errors.New("empty instance uid is not allowed")
-		r.logger.Debugf(err.Error())
+		r.logger.Debugf(context.Background(), err.Error())
 		return err
 	}
 
 	err := r.sender.SetInstanceUid(agentId.NewInstanceUid)
 	if err != nil {
-		r.logger.Errorf("Error while setting instance uid: %v", err)
+		r.logger.Errorf(context.Background(), "Error while setting instance uid: %v", err)
 		return err
 	}
 
diff --git a/client/internal/wsreceiver.go b/client/internal/wsreceiver.go
index f4a3be5..30af697 100644
--- a/client/internal/wsreceiver.go
+++ b/client/internal/wsreceiver.go
@@ -50,7 +50,7 @@ out:
 		var message protobufs.ServerToAgent
 		if err := r.receiveMessage(&message); err != nil {
 			if ctx.Err() == nil && !websocket.IsCloseError(err, websocket.CloseNormalClosure) {
-				r.logger.Errorf("Unexpected error while receiving: %v", err)
+				r.logger.Errorf(ctx, "Unexpected error while receiving: %v", err)
 			}
 			break out
 		} else {
diff --git a/client/internal/wssender.go b/client/internal/wssender.go
index 3442edd..40ac937 100644
--- a/client/internal/wssender.go
+++ b/client/internal/wssender.go
@@ -33,7 +33,7 @@ func NewSender(logger types.Logger) *WSSender {
 // earlier. To stop the WSSender cancel the ctx.
 func (s *WSSender) Start(ctx context.Context, conn *websocket.Conn) error {
 	s.conn = conn
-	err := s.sendNextMessage()
+	err := s.sendNextMessage(ctx)
 
 	// Run the sender in the background.
 	s.stopped = make(chan struct{})
@@ -53,7 +53,7 @@ out:
 	for {
 		select {
 		case <-s.hasPendingMessage:
-			s.sendNextMessage()
+			s.sendNextMessage(ctx)
 
 		case <-ctx.Done():
 			break out
@@ -63,18 +63,18 @@ out:
 	close(s.stopped)
 }
 
-func (s *WSSender) sendNextMessage() error {
+func (s *WSSender) sendNextMessage(ctx context.Context) error {
 	msgToSend := s.nextMessage.PopPending()
 	if msgToSend != nil && !proto.Equal(msgToSend, &protobufs.AgentToServer{}) {
 		// There is a pending message and the message has some fields populated.
-		return s.sendMessage(msgToSend)
+		return s.sendMessage(ctx, msgToSend)
 	}
 	return nil
 }
 
-func (s *WSSender) sendMessage(msg *protobufs.AgentToServer) error {
+func (s *WSSender) sendMessage(ctx context.Context, msg *protobufs.AgentToServer) error {
 	if err := internal.WriteWSMessage(s.conn, msg); err != nil {
-		s.logger.Errorf("Cannot write WS message: %v", err)
+		s.logger.Errorf(ctx, "Cannot write WS message: %v", err)
 		// TODO: check if it is a connection error then propagate error back to Client and reconnect.
 		return err
 	}
diff --git a/client/types/logger.go b/client/types/logger.go
index 0ba45ac..1c4ad28 100644
--- a/client/types/logger.go
+++ b/client/types/logger.go
@@ -1,7 +1,9 @@
 package types
 
+import "context"
+
 // Logger is the logging interface used by the OpAMP Client.
 type Logger interface {
-	Debugf(format string, v ...interface{})
-	Errorf(format string, v ...interface{})
+	Debugf(ctx context.Context, format string, v ...interface{})
+	Errorf(ctx context.Context, format string, v ...interface{})
 }
diff --git a/client/wsclient.go b/client/wsclient.go
index b40f153..15b790a 100644
--- a/client/wsclient.go
+++ b/client/wsclient.go
@@ -131,7 +131,7 @@ func (c *wsClient) tryConnectOnce(ctx context.Context) (err error, retryAfter sh
 			c.common.Callbacks.OnConnectFailed(err)
 		}
 		if resp != nil {
-			c.common.Logger.Errorf("Server responded with status=%v", resp.Status)
+			c.common.Logger.Errorf(ctx, "Server responded with status=%v", resp.Status)
 			duration := sharedinternal.ExtractRetryAfterHeader(resp)
 			return err, duration
 		}
@@ -168,10 +168,10 @@ func (c *wsClient) ensureConnected(ctx context.Context) error {
 			{
 				if err, retryAfter := c.tryConnectOnce(ctx); err != nil {
 					if errors.Is(err, context.Canceled) {
-						c.common.Logger.Debugf("Client is stopped, will not try anymore.")
+						c.common.Logger.Debugf(ctx, "Client is stopped, will not try anymore.")
 						return err
 					} else {
-						c.common.Logger.Errorf("Connection failed (%v), will retry.", err)
+						c.common.Logger.Errorf(ctx, "Connection failed (%v), will retry.", err)
 					}
 					// Retry again a bit later.
 
@@ -189,7 +189,7 @@ func (c *wsClient) ensureConnected(ctx context.Context) error {
 			}
 
 		case <-ctx.Done():
-			c.common.Logger.Debugf("Client is stopped, will not try anymore.")
+			c.common.Logger.Debugf(ctx, "Client is stopped, will not try anymore.")
 			timer.Stop()
 			return ctx.Err()
 		}
@@ -218,7 +218,7 @@ func (c *wsClient) runOneCycle(ctx context.Context) {
 	// Prepare the first status report.
 	err := c.common.PrepareFirstMessage(ctx)
 	if err != nil {
-		c.common.Logger.Errorf("cannot prepare the first message:%v", err)
+		c.common.Logger.Errorf(ctx, "cannot prepare the first message:%v", err)
 		return
 	}
 
@@ -228,7 +228,7 @@ func (c *wsClient) runOneCycle(ctx context.Context) {
 	// Connected successfully. Start the sender. This will also send the first
 	// status report.
 	if err := c.sender.Start(procCtx, c.conn); err != nil {
-		c.common.Logger.Errorf("Failed to send first status report: %v", err)
+		c.common.Logger.Errorf(procCtx, "Failed to send first status report: %v", err)
 		// We could not send the report, the only thing we can do is start over.
 		_ = c.conn.Close()
 		procCancel()
diff --git a/go.mod b/go.mod
index 4345640..ad9ff0c 100644
--- a/go.mod
+++ b/go.mod
@@ -4,16 +4,17 @@ go 1.20
 
 require (
 	github.com/cenkalti/backoff/v4 v4.2.1
-	github.com/gorilla/websocket v1.4.2
+	github.com/gorilla/websocket v1.5.1
 	github.com/oklog/ulid/v2 v2.1.0
-	github.com/stretchr/testify v1.7.0
-	google.golang.org/protobuf v1.27.1
+	github.com/stretchr/testify v1.8.4
+	google.golang.org/protobuf v1.32.0
 )
 
 require (
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/google/go-cmp v0.5.6 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
+	golang.org/x/net v0.17.0 // indirect
 	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
-	gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
diff --git a/go.sum b/go.sum
index 37be215..02cdf81 100644
--- a/go.sum
+++ b/go.sum
@@ -1,29 +1,26 @@
 github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
 github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
-github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
-github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
-github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
+github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
 github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU=
 github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
 github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
-github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
+golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
-google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
-google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
+google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/internal/examples/agent/agent/agent.go b/internal/examples/agent/agent/agent.go
index 3bef4e9..514dfd4 100644
--- a/internal/examples/agent/agent/agent.go
+++ b/internal/examples/agent/agent/agent.go
@@ -81,12 +81,12 @@ func NewAgent(logger types.Logger, agentType string, agentVersion string) *Agent
 	}
 
 	agent.createAgentIdentity()
-	agent.logger.Debugf("Agent starting, id=%v, type=%s, version=%s.",
+	agent.logger.Debugf(context.Background(), "Agent starting, id=%v, type=%s, version=%s.",
 		agent.instanceId.String(), agentType, agentVersion)
 
 	agent.loadLocalConfig()
 	if err := agent.connect(); err != nil {
-		agent.logger.Errorf("Cannot connect OpAMP client: %v", err)
+		agent.logger.Errorf(context.Background(), "Cannot connect OpAMP client: %v", err)
 		return nil
 	}
 
@@ -110,13 +110,13 @@ func (agent *Agent) connect() error {
 		InstanceUid:    agent.instanceId.String(),
 		Callbacks: types.CallbacksStruct{
 			OnConnectFunc: func() {
-				agent.logger.Debugf("Connected to the server.")
+				agent.logger.Debugf(context.Background(), "Connected to the server.")
 			},
 			OnConnectFailedFunc: func(err error) {
-				agent.logger.Errorf("Failed to connect to the server: %v", err)
+				agent.logger.Errorf(context.Background(), "Failed to connect to the server: %v", err)
 			},
 			OnErrorFunc: func(err *protobufs.ServerErrorResponse) {
-				agent.logger.Errorf("Server returned an error response: %v", err.ErrorMessage)
+				agent.logger.Errorf(context.Background(), "Server returned an error response: %v", err.ErrorMessage)
 			},
 			SaveRemoteConfigStatusFunc: func(_ context.Context, status *protobufs.RemoteConfigStatus) {
 				agent.remoteConfigStatus = status
@@ -148,20 +148,20 @@ func (agent *Agent) connect() error {
 	// become known and can be checked.
 	agent.requestClientCertificate()
 
-	agent.logger.Debugf("Starting OpAMP client...")
+	agent.logger.Debugf(context.Background(), "Starting OpAMP client...")
 
 	err = agent.opampClient.Start(context.Background(), settings)
 	if err != nil {
 		return err
 	}
 
-	agent.logger.Debugf("OpAMP Client started.")
+	agent.logger.Debugf(context.Background(), "OpAMP Client started.")
 
 	return nil
 }
 
 func (agent *Agent) disconnect() {
-	agent.logger.Debugf("Disconnecting from server...")
+	agent.logger.Debugf(context.Background(), "Disconnecting from server...")
 	agent.opampClient.Stop(context.Background())
 }
 
@@ -210,7 +210,7 @@ func (agent *Agent) createAgentIdentity() {
 }
 
 func (agent *Agent) updateAgentIdentity(instanceId ulid.ULID) {
-	agent.logger.Debugf("Agent identify is being changed from id=%v to id=%v",
+	agent.logger.Debugf(context.Background(), "Agent identify is being changed from id=%v to id=%v",
 		agent.instanceId.String(),
 		instanceId.String())
 	agent.instanceId = instanceId
@@ -246,7 +246,7 @@ func (agent *Agent) composeEffectiveConfig() *protobufs.EffectiveConfig {
 func (agent *Agent) initMeter(settings *protobufs.TelemetryConnectionSettings) {
 	reporter, err := NewMetricReporter(agent.logger, settings, agent.agentType, agent.agentVersion, agent.instanceId)
 	if err != nil {
-		agent.logger.Errorf("Cannot collect metrics: %v", err)
+		agent.logger.Errorf(context.Background(), "Cannot collect metrics: %v", err)
 		return
 	}
 
@@ -287,7 +287,7 @@ func (agent *Agent) applyRemoteConfig(config *protobufs.AgentRemoteConfig) (conf
 		return false, nil
 	}
 
-	agent.logger.Debugf("Received remote config from server, hash=%x.", config.ConfigHash)
+	agent.logger.Debugf(context.Background(), "Received remote config from server, hash=%x.", config.ConfigHash)
 
 	// Begin with local config. We will later merge received configs on top of it.
 	var k = koanf.New(".")
@@ -341,7 +341,7 @@ func (agent *Agent) applyRemoteConfig(config *protobufs.AgentRemoteConfig) (conf
 	newEffectiveConfig := string(effectiveConfigBytes)
 	configChanged = false
 	if agent.effectiveConfig != newEffectiveConfig {
-		agent.logger.Debugf("Effective config changed. Need to report to server.")
+		agent.logger.Debugf(context.Background(), "Effective config changed. Need to report to server.")
 		agent.effectiveConfig = newEffectiveConfig
 		configChanged = true
 	}
@@ -350,7 +350,7 @@ func (agent *Agent) applyRemoteConfig(config *protobufs.AgentRemoteConfig) (conf
 }
 
 func (agent *Agent) Shutdown() {
-	agent.logger.Debugf("Agent shutting down...")
+	agent.logger.Debugf(context.Background(), "Agent shutting down...")
 	if agent.opampClient != nil {
 		_ = agent.opampClient.Stop(context.Background())
 	}
@@ -370,7 +370,7 @@ func (agent *Agent) requestClientCertificate() {
 	// Generate a keypair for new client cert.
 	clientCertKeyPair, err := rsa.GenerateKey(cryptorand.Reader, 4096)
 	if err != nil {
-		agent.logger.Errorf("Cannot generate keypair: %v", err)
+		agent.logger.Errorf(context.Background(), "Cannot generate keypair: %v", err)
 		return
 	}
 
@@ -401,7 +401,7 @@ func (agent *Agent) requestClientCertificate() {
 
 	derBytes, err := x509.CreateCertificateRequest(cryptorand.Reader, &template, clientCertKeyPair)
 	if err != nil {
-		agent.logger.Errorf("Failed to create certificate request: %s", err)
+		agent.logger.Errorf(context.Background(), "Failed to create certificate request: %s", err)
 		return
 	}
 
@@ -426,7 +426,7 @@ func (agent *Agent) requestClientCertificate() {
 		},
 	)
 	if err != nil {
-		agent.logger.Errorf("Failed to send CSR to server: %s", err)
+		agent.logger.Errorf(context.Background(), "Failed to send CSR to server: %s", err)
 		return
 	}
 
@@ -461,7 +461,7 @@ func (agent *Agent) onMessage(ctx context.Context, msg *types.MessageData) {
 	if msg.AgentIdentification != nil {
 		newInstanceId, err := ulid.Parse(msg.AgentIdentification.NewInstanceUid)
 		if err != nil {
-			agent.logger.Errorf(err.Error())
+			agent.logger.Errorf(ctx, err.Error())
 		}
 		agent.updateAgentIdentity(newInstanceId)
 	}
@@ -469,7 +469,7 @@ func (agent *Agent) onMessage(ctx context.Context, msg *types.MessageData) {
 	if configChanged {
 		err := agent.opampClient.UpdateEffectiveConfig(ctx)
 		if err != nil {
-			agent.logger.Errorf(err.Error())
+			agent.logger.Errorf(context.Background(), err.Error())
 		}
 	}
 
@@ -483,22 +483,22 @@ func (agent *Agent) onMessage(ctx context.Context, msg *types.MessageData) {
 	agent.requestClientCertificate()
 }
 
-func (agent *Agent) tryChangeOpAMPCert(cert *tls.Certificate) {
-	agent.logger.Debugf("Reconnecting to verify offered client certificate.\n")
+func (agent *Agent) tryChangeOpAMPCert(ctx context.Context, cert *tls.Certificate) {
+	agent.logger.Debugf(ctx, "Reconnecting to verify offered client certificate.\n")
 
 	agent.disconnect()
 
 	agent.opampClientCert = cert
 	if err := agent.connect(); err != nil {
-		agent.logger.Errorf("Cannot connect using offered certificate: %s. Ignoring the offer\n", err)
+		agent.logger.Errorf(ctx, "Cannot connect using offered certificate: %s. Ignoring the offer\n", err)
 		agent.opampClientCert = nil
 
 		if err := agent.connect(); err != nil {
-			agent.logger.Errorf("Unable to reconnect after restoring client certificate: %v\n", err)
+			agent.logger.Errorf(ctx, "Unable to reconnect after restoring client certificate: %v\n", err)
 		}
 	}
 
-	agent.logger.Debugf("Successfully connected to server. Accepting new client certificate.\n")
+	agent.logger.Debugf(ctx, "Successfully connected to server. Accepting new client certificate.\n")
 
 	// TODO: we can also persist the successfully accepted certificate and use it when the
 	// agent connects to the server after the restart.
@@ -506,7 +506,7 @@ func (agent *Agent) tryChangeOpAMPCert(cert *tls.Certificate) {
 
 func (agent *Agent) onOpampConnectionSettings(ctx context.Context, settings *protobufs.OpAMPConnectionSettings) error {
 	if settings == nil || settings.Certificate == nil {
-		agent.logger.Debugf("Received nil certificate offer, ignoring.\n")
+		agent.logger.Debugf(ctx, "Received nil certificate offer, ignoring.\n")
 		return nil
 	}
 
@@ -516,7 +516,7 @@ func (agent *Agent) onOpampConnectionSettings(ctx context.Context, settings *pro
 	}
 
 	// TODO: also use settings.DestinationEndpoint and settings.Headers for future connections.
-	go agent.tryChangeOpAMPCert(cert)
+	go agent.tryChangeOpAMPCert(ctx, cert)
 
 	return nil
 }
@@ -548,7 +548,7 @@ func (agent *Agent) getCertFromSettings(certificate *protobufs.TLSCertificate) (
 	}
 
 	if err != nil {
-		agent.logger.Errorf("Received invalid certificate offer: %s\n", err.Error())
+		agent.logger.Errorf(context.Background(), "Received invalid certificate offer: %s\n", err.Error())
 		return nil, err
 	}
 
@@ -556,10 +556,10 @@ func (agent *Agent) getCertFromSettings(certificate *protobufs.TLSCertificate) (
 		caCertPB, _ := pem.Decode(certificate.CaPublicKey)
 		caCert, err := x509.ParseCertificate(caCertPB.Bytes)
 		if err != nil {
-			agent.logger.Errorf("Cannot parse CA cert: %v", err)
+			agent.logger.Errorf(context.Background(), "Cannot parse CA cert: %v", err)
 			return nil, err
 		}
-		agent.logger.Debugf("Received offer signed by CA: %v", caCert.Subject)
+		agent.logger.Debugf(context.Background(), "Received offer signed by CA: %v", caCert.Subject)
 		// TODO: we can verify the CA's identity here (to match our CA as we know it).
 	}
 
diff --git a/internal/examples/agent/agent/logger.go b/internal/examples/agent/agent/logger.go
index d161f34..2e5c5ef 100644
--- a/internal/examples/agent/agent/logger.go
+++ b/internal/examples/agent/agent/logger.go
@@ -1,15 +1,22 @@
 package agent
 
-import "log"
+import (
+	"context"
+	"log"
+
+	"github.com/open-telemetry/opamp-go/client/types"
+)
+
+var _ types.Logger = &Logger{}
 
 type Logger struct {
 	Logger *log.Logger
 }
 
-func (l *Logger) Debugf(format string, v ...interface{}) {
+func (l *Logger) Debugf(_ context.Context, format string, v ...interface{}) {
 	l.Logger.Printf(format, v...)
 }
 
-func (l *Logger) Errorf(format string, v ...interface{}) {
+func (l *Logger) Errorf(_ context.Context, format string, v ...interface{}) {
 	l.Logger.Printf(format, v...)
 }
diff --git a/internal/examples/agent/agent/metricreporter.go b/internal/examples/agent/agent/metricreporter.go
index 524e8dc..74e06a0 100644
--- a/internal/examples/agent/agent/metricreporter.go
+++ b/internal/examples/agent/agent/metricreporter.go
@@ -148,10 +148,10 @@ func NewMetricReporter(
 	return reporter, nil
 }
 
-func (reporter *MetricReporter) processCpuTimeFunc(_ context.Context, result metric.Float64ObserverResult) {
+func (reporter *MetricReporter) processCpuTimeFunc(ctx context.Context, result metric.Float64ObserverResult) {
 	times, err := reporter.process.Times()
 	if err != nil {
-		reporter.logger.Errorf("Cannot get process CPU times: %v", err)
+		reporter.logger.Errorf(ctx, "Cannot get process CPU times: %v", err)
 	}
 
 	// Report process CPU times, but also add some randomness to make it interesting for demo.
@@ -160,10 +160,10 @@ func (reporter *MetricReporter) processCpuTimeFunc(_ context.Context, result met
 	result.Observe(math.Min(times.Iowait+rand.Float64(), 1), attribute.String("state", "wait"))
 }
 
-func (reporter *MetricReporter) processMemoryPhysicalFunc(_ context.Context, result metric.Int64ObserverResult) {
+func (reporter *MetricReporter) processMemoryPhysicalFunc(ctx context.Context, result metric.Int64ObserverResult) {
 	memory, err := reporter.process.MemoryInfo()
 	if err != nil {
-		reporter.logger.Errorf("Cannot get process memory information: %v", err)
+		reporter.logger.Errorf(ctx, "Cannot get process memory information: %v", err)
 		return
 	}
 
diff --git a/internal/examples/go.mod b/internal/examples/go.mod
index b20825c..1e0866a 100644
--- a/internal/examples/go.mod
+++ b/internal/examples/go.mod
@@ -8,14 +8,14 @@ require (
 	github.com/oklog/ulid/v2 v2.1.0
 	github.com/open-telemetry/opamp-go v0.1.0
 	github.com/shirou/gopsutil v3.21.11+incompatible
-	github.com/stretchr/testify v1.7.0
+	github.com/stretchr/testify v1.8.4
 	go.opentelemetry.io/otel v1.3.0
 	go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.26.0
 	go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.26.0
 	go.opentelemetry.io/otel/metric v0.26.0
 	go.opentelemetry.io/otel/sdk v1.3.0
 	go.opentelemetry.io/otel/sdk/metric v0.26.0
-	google.golang.org/protobuf v1.27.1
+	google.golang.org/protobuf v1.32.0
 )
 
 require (
@@ -25,7 +25,7 @@ require (
 	github.com/go-logr/stdr v1.2.0 // indirect
 	github.com/go-ole/go-ole v1.2.6 // indirect
 	github.com/golang/protobuf v1.5.2 // indirect
-	github.com/gorilla/websocket v1.4.2 // indirect
+	github.com/gorilla/websocket v1.5.1 // indirect
 	github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
 	github.com/mitchellh/copystructure v1.2.0 // indirect
 	github.com/mitchellh/mapstructure v1.4.1 // indirect
@@ -39,13 +39,13 @@ require (
 	go.opentelemetry.io/otel/sdk/export/metric v0.26.0 // indirect
 	go.opentelemetry.io/otel/trace v1.3.0 // indirect
 	go.opentelemetry.io/proto/otlp v0.11.0 // indirect
-	golang.org/x/net v0.0.0-20200822124328-c89045814202 // indirect
-	golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71 // indirect
-	golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db // indirect
+	golang.org/x/net v0.17.0 // indirect
+	golang.org/x/sys v0.13.0 // indirect
+	golang.org/x/text v0.13.0 // indirect
 	google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect
 	google.golang.org/grpc v1.42.0 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
-	gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
 
 replace github.com/open-telemetry/opamp-go => ../../
diff --git a/internal/examples/go.sum b/internal/examples/go.sum
index f54e660..4f0b0d9 100644
--- a/internal/examples/go.sum
+++ b/internal/examples/go.sum
@@ -80,8 +80,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
-github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
+github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
 github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
 github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@@ -153,8 +153,9 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
-github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
 github.com/tklauser/go-sysconf v0.3.9 h1:JeUVdAOWhhxVcU6Eqr/ATFHgXk/mmiItdKeJPev3vTo=
 github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs=
 github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ=
@@ -197,8 +198,9 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA=
 golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
+golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -217,11 +219,13 @@ golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71 h1:ikCpsnYR+Ew0vu99XlDp55lGgDJdIMx3f4a18jfse/s=
 golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db h1:6/JqlYfC1CCaLnGceQTI+sDGhC9UBSPAsBqI0Gun6kU=
 golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -260,8 +264,9 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
 google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
 google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
+google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
 gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -271,7 +276,8 @@ gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/internal/examples/server/opampsrv/logger.go b/internal/examples/server/opampsrv/logger.go
index 270fdaa..d65ad9f 100644
--- a/internal/examples/server/opampsrv/logger.go
+++ b/internal/examples/server/opampsrv/logger.go
@@ -1,15 +1,22 @@
 package opampsrv
 
-import "log"
+import (
+	"context"
+	"log"
+
+	"github.com/open-telemetry/opamp-go/client/types"
+)
+
+var _ types.Logger = &Logger{}
 
 type Logger struct {
 	logger *log.Logger
 }
 
-func (l *Logger) Debugf(format string, v ...interface{}) {
+func (l *Logger) Debugf(ctx context.Context, format string, v ...interface{}) {
 	l.logger.Printf(format, v...)
 }
 
-func (l *Logger) Errorf(format string, v ...interface{}) {
+func (l *Logger) Errorf(ctx context.Context, format string, v ...interface{}) {
 	l.logger.Printf(format, v...)
 }
diff --git a/internal/examples/server/opampsrv/opampsrv.go b/internal/examples/server/opampsrv/opampsrv.go
index 070541f..f2945d7 100644
--- a/internal/examples/server/opampsrv/opampsrv.go
+++ b/internal/examples/server/opampsrv/opampsrv.go
@@ -61,12 +61,12 @@ func (srv *Server) Start() {
 		"../../certs/server_certs/server.key.pem",
 	)
 	if err != nil {
-		srv.logger.Debugf("Could not load TLS config, working without TLS: %v", err.Error())
+		srv.logger.Debugf(context.Background(), "Could not load TLS config, working without TLS: %v", err.Error())
 	}
 	settings.TLSConfig = tlsConfig
 
 	if err := srv.opampSrv.Start(settings); err != nil {
-		srv.logger.Errorf("OpAMP server start fail: %v", err.Error())
+		srv.logger.Errorf(context.Background(), "OpAMP server start fail: %v", err.Error())
 		os.Exit(1)
 	}
 }
diff --git a/internal/examples/supervisor/main.go b/internal/examples/supervisor/main.go
index 67f89d4..998c826 100644
--- a/internal/examples/supervisor/main.go
+++ b/internal/examples/supervisor/main.go
@@ -1,6 +1,7 @@
 package main
 
 import (
+	"context"
 	"log"
 	"os"
 	"os/signal"
@@ -12,7 +13,7 @@ func main() {
 	logger := &supervisor.Logger{Logger: log.Default()}
 	supervisor, err := supervisor.NewSupervisor(logger)
 	if err != nil {
-		logger.Errorf(err.Error())
+		logger.Errorf(context.Background(), err.Error())
 		os.Exit(-1)
 		return
 	}
diff --git a/internal/examples/supervisor/supervisor/commander/commander.go b/internal/examples/supervisor/supervisor/commander/commander.go
index 1d7dc13..bd2f1f6 100644
--- a/internal/examples/supervisor/supervisor/commander/commander.go
+++ b/internal/examples/supervisor/supervisor/commander/commander.go
@@ -41,7 +41,7 @@ func NewCommander(logger types.Logger, cfg *config.Agent, args ...string) (*Comm
 // Start the Agent and begin watching the process.
 // Agent's stdout and stderr are written to a file.
 func (c *Commander) Start(ctx context.Context) error {
-	c.logger.Debugf("Starting agent %s", c.cfg.Executable)
+	c.logger.Debugf(ctx, "Starting agent %s", c.cfg.Executable)
 
 	logFilePath := "agent.log"
 	logFile, err := os.Create(logFilePath)
@@ -62,7 +62,7 @@ func (c *Commander) Start(ctx context.Context) error {
 		return err
 	}
 
-	c.logger.Debugf("Agent process started, PID=%d", c.cmd.Process.Pid)
+	c.logger.Debugf(ctx, "Agent process started, PID=%d", c.cmd.Process.Pid)
 	atomic.StoreInt64(&c.running, 1)
 
 	go c.watch()
@@ -121,7 +121,7 @@ func (c *Commander) Stop(ctx context.Context) error {
 		return nil
 	}
 
-	c.logger.Debugf("Stopping agent process, PID=%v", c.cmd.Process.Pid)
+	c.logger.Debugf(ctx, "Stopping agent process, PID=%v", c.cmd.Process.Pid)
 
 	// Gracefully signal process to stop.
 	if err := c.cmd.Process.Signal(syscall.SIGTERM); err != nil {
@@ -143,12 +143,12 @@ func (c *Commander) Stop(ctx context.Context) error {
 			break
 		case <-finished:
 			// Process is successfully finished.
-			c.logger.Debugf("Agent process PID=%v successfully stopped.", c.cmd.Process.Pid)
+			c.logger.Debugf(ctx, "Agent process PID=%v successfully stopped.", c.cmd.Process.Pid)
 			return
 		}
 
 		// Time is out. Kill the process.
-		c.logger.Debugf(
+		c.logger.Debugf(ctx,
 			"Agent process PID=%d is not responding to SIGTERM. Sending SIGKILL to kill forcedly.",
 			c.cmd.Process.Pid)
 		if innerErr = c.cmd.Process.Signal(syscall.SIGKILL); innerErr != nil {
diff --git a/internal/examples/supervisor/supervisor/logger.go b/internal/examples/supervisor/supervisor/logger.go
index 5bf6e11..a9ba75d 100644
--- a/internal/examples/supervisor/supervisor/logger.go
+++ b/internal/examples/supervisor/supervisor/logger.go
@@ -1,15 +1,22 @@
 package supervisor
 
-import "log"
+import (
+	"context"
+	"log"
+
+	"github.com/open-telemetry/opamp-go/client/types"
+)
+
+var _ types.Logger = &Logger{}
 
 type Logger struct {
 	Logger *log.Logger
 }
 
-func (l *Logger) Debugf(format string, v ...interface{}) {
+func (l *Logger) Debugf(ctx context.Context, format string, v ...interface{}) {
 	l.Logger.Printf(format, v...)
 }
 
-func (l *Logger) Errorf(format string, v ...interface{}) {
+func (l *Logger) Errorf(ctx context.Context, format string, v ...interface{}) {
 	l.Logger.Printf(format, v...)
 }
diff --git a/internal/examples/supervisor/supervisor/supervisor.go b/internal/examples/supervisor/supervisor/supervisor.go
index 489a611..c51b3e6 100644
--- a/internal/examples/supervisor/supervisor/supervisor.go
+++ b/internal/examples/supervisor/supervisor/supervisor.go
@@ -90,7 +90,7 @@ func NewSupervisor(logger types.Logger) (*Supervisor, error) {
 	}
 
 	s.createInstanceId()
-	logger.Debugf("Supervisor starting, id=%v, type=%s, version=%s.",
+	logger.Debugf(context.Background(), "Supervisor starting, id=%v, type=%s, version=%s.",
 		s.instanceId.String(), agentType, agentVersion)
 
 	s.loadAgentEffectiveConfig()
@@ -141,13 +141,13 @@ func (s *Supervisor) startOpAMP() error {
 		InstanceUid: s.instanceId.String(),
 		Callbacks: types.CallbacksStruct{
 			OnConnectFunc: func() {
-				s.logger.Debugf("Connected to the server.")
+				s.logger.Debugf(context.Background(), "Connected to the server.")
 			},
 			OnConnectFailedFunc: func(err error) {
-				s.logger.Errorf("Failed to connect to the server: %v", err)
+				s.logger.Errorf(context.Background(), "Failed to connect to the server: %v", err)
 			},
 			OnErrorFunc: func(err *protobufs.ServerErrorResponse) {
-				s.logger.Errorf("Server returned an error response: %v", err.ErrorMessage)
+				s.logger.Errorf(context.Background(), "Server returned an error response: %v", err.ErrorMessage)
 			},
 			GetEffectiveConfigFunc: func(ctx context.Context) (*protobufs.EffectiveConfig, error) {
 				return s.createEffectiveConfigMsg(), nil
@@ -170,14 +170,14 @@ func (s *Supervisor) startOpAMP() error {
 		return err
 	}
 
-	s.logger.Debugf("Starting OpAMP client...")
+	s.logger.Debugf(context.Background(), "Starting OpAMP client...")
 
 	err = s.opampClient.Start(context.Background(), settings)
 	if err != nil {
 		return err
 	}
 
-	s.logger.Debugf("OpAMP Client started.")
+	s.logger.Debugf(context.Background(), "OpAMP Client started.")
 
 	return nil
 }
@@ -283,10 +283,10 @@ func (s *Supervisor) setupOwnMetrics(ctx context.Context, settings *protobufs.Te
 	var cfg string
 	if settings.DestinationEndpoint == "" {
 		// No destination. Disable metric collection.
-		s.logger.Debugf("Disabling own metrics pipeline in the config")
+		s.logger.Debugf(ctx, "Disabling own metrics pipeline in the config")
 		cfg = ""
 	} else {
-		s.logger.Debugf("Enabling own metrics pipeline in the config")
+		s.logger.Debugf(ctx, "Enabling own metrics pipeline in the config")
 
 		// TODO: choose the scraping port dynamically instead of hard-coding to 8888.
 		cfg = fmt.Sprintf(
@@ -387,7 +387,7 @@ func (s *Supervisor) composeEffectiveConfig(config *protobufs.AgentRemoteConfig)
 	newEffectiveConfig := string(effectiveConfigBytes)
 	configChanged = false
 	if s.effectiveConfig.Load().(string) != newEffectiveConfig {
-		s.logger.Debugf("Effective config changed.")
+		s.logger.Debugf(context.Background(), "Effective config changed.")
 		s.effectiveConfig.Store(newEffectiveConfig)
 		configChanged = true
 	}
@@ -401,7 +401,7 @@ func (s *Supervisor) recalcEffectiveConfig() (configChanged bool, err error) {
 
 	configChanged, err = s.composeEffectiveConfig(s.remoteConfig)
 	if err != nil {
-		s.logger.Errorf("Error composing effective config. Ignoring received config: %v", err)
+		s.logger.Errorf(context.Background(), "Error composing effective config. Ignoring received config: %v", err)
 		return configChanged, err
 	}
 
@@ -412,7 +412,7 @@ func (s *Supervisor) startAgent() {
 	err := s.commander.Start(context.Background())
 	if err != nil {
 		errMsg := fmt.Sprintf("Cannot start the agent: %v", err)
-		s.logger.Errorf(errMsg)
+		s.logger.Errorf(context.Background(), errMsg)
 		s.opampClient.SetHealth(&protobufs.ComponentHealth{Healthy: false, LastError: errMsg})
 		return
 	}
@@ -455,15 +455,15 @@ func (s *Supervisor) healthCheck() {
 	if err != nil {
 		health.Healthy = false
 		health.LastError = err.Error()
-		s.logger.Errorf("Agent is not healthy: %s", health.LastError)
+		s.logger.Errorf(ctx, "Agent is not healthy: %s", health.LastError)
 	} else {
 		health.Healthy = true
-		s.logger.Debugf("Agent is healthy.")
+		s.logger.Debugf(ctx, "Agent is healthy.")
 	}
 
 	// Report via OpAMP.
 	if err2 := s.opampClient.SetHealth(health); err2 != nil {
-		s.logger.Errorf("Could not report health. SetHealth returned: %v", err2)
+		s.logger.Errorf(ctx, "Could not report health. SetHealth returned: %v", err2)
 		return
 	}
 
@@ -496,7 +496,7 @@ func (s *Supervisor) runAgentProcess() {
 				"Agent process PID=%d exited unexpectedly, exit code=%d. Will restart in a bit...",
 				s.commander.Pid(), s.commander.ExitCode(),
 			)
-			s.logger.Debugf(errMsg)
+			s.logger.Debugf(context.Background(), errMsg)
 			s.opampClient.SetHealth(&protobufs.ComponentHealth{Healthy: false, LastError: errMsg})
 
 			// TODO: decide why the agent stopped. If it was due to bad config, report it to server.
@@ -515,7 +515,7 @@ func (s *Supervisor) runAgentProcess() {
 }
 
 func (s *Supervisor) stopAgentApplyConfig() {
-	s.logger.Debugf("Stopping the agent to apply new config.")
+	s.logger.Debugf(context.Background(), "Stopping the agent to apply new config.")
 	cfg := s.effectiveConfig.Load().(string)
 	s.commander.Stop(context.Background())
 	s.writeEffectiveConfigToFile(cfg, s.effectiveConfigFilePath)
@@ -524,7 +524,7 @@ func (s *Supervisor) stopAgentApplyConfig() {
 func (s *Supervisor) writeEffectiveConfigToFile(cfg string, filePath string) {
 	f, err := os.Create(filePath)
 	if err != nil {
-		s.logger.Errorf("Cannot write effective config file: %v", err)
+		s.logger.Errorf(context.Background(), "Cannot write effective config file: %v", err)
 	}
 	defer f.Close()
 
@@ -532,7 +532,7 @@ func (s *Supervisor) writeEffectiveConfigToFile(cfg string, filePath string) {
 }
 
 func (s *Supervisor) Shutdown() {
-	s.logger.Debugf("Supervisor shutting down...")
+	s.logger.Debugf(context.Background(), "Supervisor shutting down...")
 	if s.commander != nil {
 		s.commander.Stop(context.Background())
 	}
@@ -550,7 +550,7 @@ func (s *Supervisor) onMessage(ctx context.Context, msg *types.MessageData) {
 	configChanged := false
 	if msg.RemoteConfig != nil {
 		s.remoteConfig = msg.RemoteConfig
-		s.logger.Debugf("Received remote config from server, hash=%x.", s.remoteConfig.ConfigHash)
+		s.logger.Debugf(ctx, "Received remote config from server, hash=%x.", s.remoteConfig.ConfigHash)
 
 		var err error
 		configChanged, err = s.recalcEffectiveConfig()
@@ -575,10 +575,10 @@ func (s *Supervisor) onMessage(ctx context.Context, msg *types.MessageData) {
 	if msg.AgentIdentification != nil {
 		newInstanceId, err := ulid.Parse(msg.AgentIdentification.NewInstanceUid)
 		if err != nil {
-			s.logger.Errorf(err.Error())
+			s.logger.Errorf(ctx, err.Error())
 		}
 
-		s.logger.Debugf("Agent identify is being changed from id=%v to id=%v",
+		s.logger.Debugf(ctx, "Agent identify is being changed from id=%v to id=%v",
 			s.instanceId.String(),
 			newInstanceId.String())
 		s.instanceId = newInstanceId
@@ -590,10 +590,10 @@ func (s *Supervisor) onMessage(ctx context.Context, msg *types.MessageData) {
 	if configChanged {
 		err := s.opampClient.UpdateEffectiveConfig(ctx)
 		if err != nil {
-			s.logger.Errorf(err.Error())
+			s.logger.Errorf(ctx, err.Error())
 		}
 
-		s.logger.Debugf("Config is changed. Signal to restart the agent.")
+		s.logger.Debugf(ctx, "Config is changed. Signal to restart the agent.")
 		// Signal that there is a new config.
 		select {
 		case s.hasNewConfig <- struct{}{}:
diff --git a/internal/noplogger.go b/internal/noplogger.go
index a575c5d..a2b2ea2 100644
--- a/internal/noplogger.go
+++ b/internal/noplogger.go
@@ -1,6 +1,14 @@
 package internal
 
+import (
+	"context"
+
+	"github.com/open-telemetry/opamp-go/client/types"
+)
+
+var _ types.Logger = &NopLogger{}
+
 type NopLogger struct{}
 
-func (l *NopLogger) Debugf(format string, v ...interface{}) {}
-func (l *NopLogger) Errorf(format string, v ...interface{}) {}
+func (l *NopLogger) Debugf(ctx context.Context, format string, v ...interface{}) {}
+func (l *NopLogger) Errorf(ctx context.Context, format string, v ...interface{}) {}
diff --git a/server/serverimpl.go b/server/serverimpl.go
index 27a9587..7eef066 100644
--- a/server/serverimpl.go
+++ b/server/serverimpl.go
@@ -130,7 +130,7 @@ func (s *server) startHttpServer(listenAddr string, serveFunc func(l net.Listene
 		// ErrServerClosed is expected after successful Stop(), so we won't log that
 		// particular error.
 		if err != nil && err != http.ErrServerClosed {
-			s.logger.Errorf("Error running HTTP Server: %v", err)
+			s.logger.Errorf(context.Background(), "Error running HTTP Server: %v", err)
 		}
 	}()
 
@@ -179,7 +179,7 @@ func (s *server) httpHandler(w http.ResponseWriter, req *http.Request) {
 	// No, it is a WebSocket. Upgrade it.
 	conn, err := s.wsUpgrader.Upgrade(w, req, nil)
 	if err != nil {
-		s.logger.Errorf("Cannot upgrade HTTP connection to WebSocket: %v", err)
+		s.logger.Errorf(context.Background(), "Cannot upgrade HTTP connection to WebSocket: %v", err)
 		return
 	}
 
@@ -196,7 +196,7 @@ func (s *server) handleWSConnection(wsConn *websocket.Conn, connectionCallbacks
 		defer func() {
 			err := wsConn.Close()
 			if err != nil {
-				s.logger.Errorf("error closing the WebSocket connection: %v", err)
+				s.logger.Errorf(context.Background(), "error closing the WebSocket connection: %v", err)
 			}
 		}()
 
@@ -215,15 +215,15 @@ func (s *server) handleWSConnection(wsConn *websocket.Conn, connectionCallbacks
 		mt, bytes, err := wsConn.ReadMessage()
 		if err != nil {
 			if !websocket.IsUnexpectedCloseError(err) {
-				s.logger.Errorf("Cannot read a message from WebSocket: %v", err)
+				s.logger.Errorf(context.Background(), "Cannot read a message from WebSocket: %v", err)
 				break
 			}
 			// This is a normal closing of the WebSocket connection.
-			s.logger.Debugf("Agent disconnected: %v", err)
+			s.logger.Debugf(context.Background(), "Agent disconnected: %v", err)
 			break
 		}
 		if mt != websocket.BinaryMessage {
-			s.logger.Errorf("Received unexpected message type from WebSocket: %v", mt)
+			s.logger.Errorf(context.Background(), "Received unexpected message type from WebSocket: %v", mt)
 			continue
 		}
 
@@ -231,7 +231,7 @@ func (s *server) handleWSConnection(wsConn *websocket.Conn, connectionCallbacks
 		var request protobufs.AgentToServer
 		err = internal.DecodeWSMessage(bytes, &request)
 		if err != nil {
-			s.logger.Errorf("Cannot decode message from WebSocket: %v", err)
+			s.logger.Errorf(context.Background(), "Cannot decode message from WebSocket: %v", err)
 			continue
 		}
 
@@ -242,7 +242,7 @@ func (s *server) handleWSConnection(wsConn *websocket.Conn, connectionCallbacks
 			}
 			err = agentConn.Send(context.Background(), response)
 			if err != nil {
-				s.logger.Errorf("Cannot send message to WebSocket: %v", err)
+				s.logger.Errorf(context.Background(), "Cannot send message to WebSocket: %v", err)
 			}
 		}
 	}
@@ -288,7 +288,7 @@ func compressGzip(data []byte) ([]byte, error) {
 func (s *server) handlePlainHTTPRequest(req *http.Request, w http.ResponseWriter, connectionCallbacks serverTypes.ConnectionCallbacks) {
 	bytes, err := s.readReqBody(req)
 	if err != nil {
-		s.logger.Debugf("Cannot read HTTP body: %v", err)
+		s.logger.Debugf(context.Background(), "Cannot read HTTP body: %v", err)
 		w.WriteHeader(http.StatusBadRequest)
 		return
 	}
@@ -297,7 +297,7 @@ func (s *server) handlePlainHTTPRequest(req *http.Request, w http.ResponseWriter
 	var request protobufs.AgentToServer
 	err = proto.Unmarshal(bytes, &request)
 	if err != nil {
-		s.logger.Debugf("Cannot decode message from HTTP Body: %v", err)
+		s.logger.Debugf(context.Background(), "Cannot decode message from HTTP Body: %v", err)
 		w.WriteHeader(http.StatusBadRequest)
 		return
 	}
@@ -340,7 +340,7 @@ func (s *server) handlePlainHTTPRequest(req *http.Request, w http.ResponseWriter
 	if req.Header.Get(headerAcceptEncoding) == contentEncodingGzip {
 		bytes, err = compressGzip(bytes)
 		if err != nil {
-			s.logger.Errorf("Cannot compress response: %v", err)
+			s.logger.Errorf(context.Background(), "Cannot compress response: %v", err)
 			w.WriteHeader(http.StatusInternalServerError)
 			return
 		}
@@ -349,6 +349,6 @@ func (s *server) handlePlainHTTPRequest(req *http.Request, w http.ResponseWriter
 	_, err = w.Write(bytes)
 
 	if err != nil {
-		s.logger.Debugf("Cannot send HTTP response: %v", err)
+		s.logger.Debugf(context.Background(), "Cannot send HTTP response: %v", err)
 	}
 }


Test patch:
diff --git a/client/internal/wsreceiver_test.go b/client/internal/wsreceiver_test.go
index fe0786b..bd79c7a 100644
--- a/client/internal/wsreceiver_test.go
+++ b/client/internal/wsreceiver_test.go
@@ -14,14 +14,20 @@ import (
 	"github.com/open-telemetry/opamp-go/protobufs"
 )
 
+var _ types.Logger = &TestLogger{}
+
 type TestLogger struct {
 	*testing.T
 }
 
-func (logger TestLogger) Debugf(format string, v ...interface{}) {
+func (logger TestLogger) Debugf(ctx context.Context, format string, v ...interface{}) {
 	logger.Logf(format, v...)
 }
 
+func (logger TestLogger) Errorf(ctx context.Context, format string, v ...interface{}) {
+	logger.Fatalf(format, v...)
+}
+
 type commandAction int
 
 const (