<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sergio Matone</title>
    <description>The latest articles on DEV Community by Sergio Matone (@sw360cab).</description>
    <link>https://dev.to/sw360cab</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F402699%2Fea1db561-516e-4d72-8b4a-282165a4edd3.jpeg</url>
      <title>DEV Community: Sergio Matone</title>
      <link>https://dev.to/sw360cab</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sw360cab"/>
    <language>en</language>
    <item>
      <title>OpenTelemetry - stepping into gno.land's observability tools</title>
      <dc:creator>Sergio Matone</dc:creator>
      <pubDate>Thu, 11 Dec 2025 16:05:02 +0000</pubDate>
      <link>https://dev.to/sw360cab/opentelemetry-stepping-into-gnolands-observability-tools-2fdg</link>
      <guid>https://dev.to/sw360cab/opentelemetry-stepping-into-gnolands-observability-tools-2fdg</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Disclamer: this post is a showcase originally published in &lt;a href="https://gnops.io/articles/showcases/open-telemetry/" rel="noopener noreferrer"&gt;Gnops.io&lt;/a&gt; and it's reported here as is&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What is OTel
&lt;/h2&gt;

&lt;p&gt;OpenTelemetry (OTel) is a specification that translates into APIs and SDKs for collecting and analyzing telemetry data by instrumenting code of a target application.&lt;br&gt;
Telemetries are intended in the form of three types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Metrics&lt;/code&gt;, a measurement captured at runtime.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Traces&lt;/code&gt;, the path of a request through the application.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Logs&lt;/code&gt;, a recording of an event.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Code should be instrumented, meaning making it capable of emitting telemetry data. This can be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;code-based, explicitly adopting a specific SDK and adding specific instructions into the codebase&lt;/li&gt;
&lt;li&gt;zero-code, when the code cannot be modified, by providing information about what’s happening at the edges of the application&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The relevant components in the OpenTelemetry landscapes are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data&lt;/strong&gt;: Defined in the OpenTelemetry Protocol (OTLP) and with vendor-agnostic semantic conventions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenTelemetry Collector&lt;/strong&gt;: a vendor-agnostic proxy that can receive, process, and export telemetry data. Data can be in multiple formats (OTLP, Jaeger, Prometheus) and can be exported to one or more backends&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observability Backends&lt;/strong&gt;: Specific services able to elaborate observability data using proprietary technologies (e.g. Prometheus, Jaeger, Zipkin)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All this data facilitates the analysis of the performances of a specific instrumented application, allowing the &lt;code&gt;observability&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why OTel
&lt;/h2&gt;

&lt;p&gt;OpenTelemetry is a free and open source technology and it does not depend on any other system, service or technology, in this way we can maximize the possibilities of integration with other services and technologies and minimize or totally avoid the vendor lock-in.&lt;/p&gt;
&lt;h2&gt;
  
  
  OTel in Gnoland
&lt;/h2&gt;

&lt;p&gt;First of all, the current version of Gnoland will focus on OpenTelemetry metrics, traces may come next in a short time.&lt;br&gt;
All the gno.land code has been actively instrumented to collect a predefined set of metrics, mainly &lt;code&gt;counters&lt;/code&gt; and &lt;code&gt;histograms&lt;/code&gt; and some &lt;code&gt;gauges&lt;/code&gt;. the code has been explicitly instrumented using the official &lt;a href="https://opentelemetry.io/docs/languages/go/" rel="noopener noreferrer"&gt;Go SDK&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The reference file setting up OpenTelemetry in the official &lt;a href="https://github.com/gnolang/gno" rel="noopener noreferrer"&gt;Gno repository&lt;/a&gt; is at &lt;a href="https://github.com/gnolang/gno/blob/master/tm2/pkg/telemetry/metrics/metrics.go" rel="noopener noreferrer"&gt;tm2/pkg/telemetry/metrics/metrics.go&lt;/a&gt;.&lt;br&gt;
This holds an init method which is in charge of creating the metric provider, which in turn will be collecting the instrumented metrics and exporting them periodically to either an http or a grpc endpoint.&lt;/p&gt;

&lt;p&gt;Here the relevant source code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;//Exporter setup&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="c"&gt;// Use oltp metric exporter with http/https or grpc&lt;/span&gt;
  &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Scheme&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"http"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"https"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;otlpmetrichttp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;otlpmetrichttp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithEndpointURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExporterEndpoint&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"unable to create http metrics exporter, %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;otlpmetricgrpc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;otlpmetricgrpc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithEndpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExporterEndpoint&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="n"&gt;otlpmetricgrpc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithInsecure&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"unable to create grpc metrics exporter, %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c"&gt;// creating metric provider&lt;/span&gt;
  &lt;span class="n"&gt;provider&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;sdkMetric&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewMeterProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="c"&gt;// Default period is 1m&lt;/span&gt;
    &lt;span class="n"&gt;sdkMetric&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sdkMetric&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewPeriodicReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="n"&gt;sdkMetric&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewWithAttributes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;semconv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SchemaURL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;semconv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServiceNameKey&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServiceName&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;semconv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServiceVersionKey&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"1.0.0"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;semconv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServiceInstanceIDKey&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServiceInstanceID&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c"&gt;// instrument OTel SDK&lt;/span&gt;
  &lt;span class="n"&gt;OTel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetMeterProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;meter&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Meter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MeterName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c"&gt;//...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Throughout the instrumentation part, convention over configuration has been heavily adopted. For each metric a &lt;code&gt;const&lt;/code&gt; is defined, the latter is used as key to instrument the metric, which in turn is saved into an exported &lt;code&gt;var&lt;/code&gt;, which represents the metric handler.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;numMempoolTxsKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"num_mempool_txs_hist"&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;NumMempoolTxs&lt;/span&gt; &lt;span class="n"&gt;metric&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Int64Histogram&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;NumMempoolTxs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;meter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Int64Histogram&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;numMempoolTxsKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;metric&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithDescription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"valid mempool transaction count"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"unable to create histogram, %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The instrumented item will be used somewhere else within the codebase to collect the corresponding metric.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// logTelemetry logs the mempool telemetry&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;CListMempool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;logTelemetry&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;telemetry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MetricsEnabled&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Log the total number of mempool transactions&lt;/span&gt;
&lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NumMempoolTxs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;txs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Len&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that if the telemetry is not explicitly enabled, no attempt to access the metric handler will be made.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gnoland Binary setup
&lt;/h2&gt;

&lt;p&gt;Since relevant parts of code have been already instrumented, what is missing is setting up the relevant section of OpenTelemetry in the configuration file.&lt;/p&gt;

&lt;p&gt;The following configuration items are available in Gno setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;telemetry.enabled&lt;/code&gt;, whether telemetry is enabled&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;telemtery.exporter_endpoint&lt;/code&gt;, the endpoint to export metrics to&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;telemtetry.meter_name&lt;/code&gt;, name of the meter, meaning the entity the will create instruments for the application&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;telemetry.service_instance_id&lt;/code&gt;, an instance id to identify the current instance emitting the metrics&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;telemetry.service_name&lt;/code&gt;, a generic name to identify the service running, for example a testnet or a validator set&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All the previous name references will be included in each metric emitted by the Gnoland application.&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenTelemetry in action: Orchestrating multiple services in Docker
&lt;/h2&gt;

&lt;p&gt;In order to see OpenTelemetry in action we need&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a set of observability tools&lt;/li&gt;
&lt;li&gt;an instrumentable application, Gnoland!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To the first group belong:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OpenTelemetry collector&lt;/li&gt;
&lt;li&gt;Prometheus as backend, receiving data exported by the OTel Collector&lt;/li&gt;
&lt;li&gt;Grafana as UI to visualize results in a set of visual panels, configured to elaborate data collected by Prometheus&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the application part we will use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a Gno validator that starts producing blocks&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/gnolang/supernova" rel="noopener noreferrer"&gt;Supernova&lt;/a&gt;, the stress-testing Gno networks tool, to generate some traffic as transactions created (package deployment)&lt;/li&gt;
&lt;li&gt;an RPC node, used as handy communication tier between the Gno validator and Supernova&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The complete docker compose file for the orchestrated services can be found in the &lt;code&gt;misc/telemetry&lt;/code&gt; &lt;a href="https://github.com/gnolang/gno/tree/master/misc/telemetry" rel="noopener noreferrer"&gt;directory on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can launch the services using from the Gno repository folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;misc/telemetry
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Instrumenting applications
&lt;/h3&gt;

&lt;p&gt;When using the Gnoland binary, instrumenting the application is just one step to the side. As said before it is important to just enable telemetry from config, the &lt;code&gt;telemetry.enabled&lt;/code&gt; entry, providing an endpoint to the collector, &lt;code&gt;telemetry.exporter_endpoint&lt;/code&gt;.&lt;br&gt;
Then to identify a current set of applications, like a validator set, a generic service name can be added via &lt;code&gt;telemetry.service_name&lt;/code&gt;, together with the unique name given the specific instance providing metrics, using &lt;code&gt;telemetry.service_instance_id&lt;/code&gt;.&lt;br&gt;
These last two parameters will be included as labels in each metric emitted by the instrumented application.&lt;/p&gt;

&lt;p&gt;The validator node will generate secrets, adjust config to enable telemetry and produce labeled metrics and it will also generate a genesis file having the validator itself within the validator set entries.&lt;/p&gt;

&lt;p&gt;The RPC service will wait for the validator node to be up and running and share the genesis file to boot itself. It will not only be useful as the endpoint for Supernova, but also to produce other OTel metrics, labeled with a different source service (&lt;code&gt;telemetry.service_instance_id = rpc000&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Periodically &lt;code&gt;Supernova&lt;/code&gt; will rerun and by connecting through the RPC service it will increase the amount of data that the observability stack can consume. It is possible to manually stop Supernova to avoid running it infinitely.&lt;/p&gt;
&lt;h3&gt;
  
  
  Configuring the observability stack
&lt;/h3&gt;

&lt;p&gt;As seen above the key component of the OTel observability environment is the &lt;code&gt;collector&lt;/code&gt;. It is in charge of communicating from one side with the instrumented applications and from the other side with a backend which receives exported data.&lt;/p&gt;

&lt;p&gt;We can put together what we have seen before, our collector setup will be able to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;gather metrics from a &lt;code&gt;grpc interface&lt;/code&gt; exposed by the collector itself and accessed by the application&lt;/li&gt;
&lt;li&gt;collect only metrics, no traces or logs&lt;/li&gt;
&lt;li&gt;expose data to a Prometheus backend on a given port (in this case we picked port 8090)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is the simple and final configuration of the collector&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;receivers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;otlp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;protocols&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;grpc&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.0.0.0:4317&lt;/span&gt;

&lt;span class="na"&gt;processors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;batch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;exporters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;prometheus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;collector:8090&lt;/span&gt;

&lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;telemetry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;logs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;debug"&lt;/span&gt;
  &lt;span class="na"&gt;pipelines&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metrics&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;receivers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;otlp&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;processors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;batch&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;exporters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;prometheus&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Prometheus&lt;/code&gt; will receive data from the collector. The only configuration needed is a dedicated &lt;code&gt;scraper&lt;/code&gt; connecting to the collector using the endpoint where Prometheus is expecting data to be gathered from. In this specific case we will use a local service name with the designed port, so the final endpoint will be: &lt;code&gt;collector:8090&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;scrape_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;OpenTelemetry'&lt;/span&gt;
    &lt;span class="na"&gt;static_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;collector:8090'&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Grafana&lt;/code&gt; is the last and final tier and it will in turn gather data from Prometheus at a specific port (predefined is &lt;code&gt;9090&lt;/code&gt;), by configuring a specific datasouce. Then this data should be combined in a meaningful way to be shown in several UI panels. Eventually those panels are combined together into a dashboard, the OTel dashboard in this case.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;datasources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Prometheus&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prometheus&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://prometheus:9090&lt;/span&gt;
    &lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prometheus&lt;/span&gt;
    &lt;span class="s"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The metrics exposed can be used and combined in various ways and depending on the metric type, they are provided with additional data that allows meaningful aggregations.&lt;/p&gt;

&lt;p&gt;For example a metric of type &lt;code&gt;histogram&lt;/code&gt; will expose three metrics named using convention over configuration suffixes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;_count&lt;/em&gt;&lt;/strong&gt;, counting the number of samples&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;_sum&lt;/em&gt;&lt;/strong&gt;, summing up the value of all samples&lt;/li&gt;
&lt;li&gt;a set of multiple buckets &lt;strong&gt;&lt;em&gt;_bucket&lt;/em&gt;&lt;/strong&gt; with a label &lt;strong&gt;&lt;em&gt;le&lt;/em&gt;&lt;/strong&gt;, which contains a count of all samples whose values are less than or equal to the numeric value contained in the le label.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These data can be combined together using specific expressions. One of them can be useful to understand the trend of values in a given period of time by leveraging the formula &lt;code&gt;rate(hist_sum)/rate(hist_count)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For example, referencing the metric &lt;code&gt;vm_gas_used_hist&lt;/code&gt; collecting the gas used by VM executions in Gno, can be aggregated to retrieve the average gas used, with the following formula:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vm_gas_used_hist_sum&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vm_gas_used_hist_count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Indeed for a gauge metric, like &lt;code&gt;inbound_peers_gauge&lt;/code&gt;, that holds the number of inbound peers for a validator, we can use another common expression using the &lt;code&gt;avg_over_time()&lt;/code&gt; function to obtain the average inbound peer count.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;avg_over_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inbound_peers_gauge&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Of course it is useful to add additional parameters to the previous expressions to define a time range and pointing to a specific application instance, represented by the &lt;code&gt;node&lt;/code&gt; parameter. The final expression looks like that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vm_gas_used_hist_sum&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;exported_instance&lt;/span&gt;&lt;span class="o"&gt;=~&lt;/span&gt;&lt;span class="s"&gt;"${node}"&lt;/span&gt;&lt;span class="p"&gt;}[&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;__rate_interval&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vm_gas_used_hist_count&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;exported_instance&lt;/span&gt;&lt;span class="o"&gt;=~&lt;/span&gt;&lt;span class="s"&gt;"${node}"&lt;/span&gt;&lt;span class="p"&gt;}[&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;__rate_interval&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As said &lt;code&gt;node&lt;/code&gt; is s reference variable defined in Grafana, which queries Prometheus with the following expression:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;label_values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exported_instance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;where &lt;code&gt;exported_instance&lt;/code&gt; is a label contained in each metric and defined and added by the instrumented application. In this specific case we can distinguish metrics referring to the validator node and to the RPC node.&lt;/p&gt;

&lt;h2&gt;
  
  
  Moving resource into a Kubernetes cluster
&lt;/h2&gt;

&lt;p&gt;After creating the setup in a Docker Compose environment, moving toward Kubernetes is the next expected step.&lt;br&gt;
Let's see what we have and what we need to adapt to make it working in a Kubernetes environment.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;OpenTelemetry collector&lt;/code&gt;: this will become a fresh pair of deployment/service combo in the Kube
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;open-telemetry&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;otel-run&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;otel-run&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;otel&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;otel/OpenTelemetry-collector-contrib:0.111.0&lt;/span&gt;
          &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;otlp-http&lt;/span&gt;
              &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4317&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;otlp-grpc&lt;/span&gt;
              &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4318&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;otlp-exporter&lt;/span&gt;
              &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8889&lt;/span&gt;
&lt;span class="nn"&gt;...&lt;/span&gt;

&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;otel-svc&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;monitoring&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;otel-run&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;otlp-http&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4317&lt;/span&gt;
      &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;otlp-http&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;otlp-exporter&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8889&lt;/span&gt;
      &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;otlp-exporter&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Prometheus&lt;/code&gt;: given a deployed Prometheus stack in Kubernetes (deployment + service) it only needs to be provided with the right scrape config, which should point to the endpoint made available by the OTel collector exposed by the Kubernetes service
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;OpenTelemetry'&lt;/span&gt;
    &lt;span class="na"&gt;static_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;collector:8090'&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Grafana&lt;/code&gt;: what is needed in a common Grafana configuration provided with a Prometheus datasource, is the addition of the dedicated dashboard into the list of available ones. The dashboard is not far from what was configured in the Docker Compose version.&lt;br&gt;
As long as the Prometheus datasource is available, the same dashboard configuration will display the UI panels the same way.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Gnoland binary&lt;/code&gt;: what is needed is just a valid configuration enabling OTel and pointing to the right endpoint of the OTel collector.&lt;br&gt;
This configuration should be provided at boot time, in Kubernetes this is possible by leveraging an &lt;a href="https://kubernetes.io/docs/concepts/workloads/pods/init-containers/" rel="noopener noreferrer"&gt;initContainer&lt;/a&gt;.&lt;br&gt;
In particular it is important to point to the right Kubernetes service representing the OTel collector. In order to follow the above configuration the endpoint will look like: &lt;code&gt;otel-svc.svc.cluster.local:4317&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;...&lt;/span&gt;
&lt;span class="na"&gt;initContainers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;init-config&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io/gnolang/gno/gnoland:{{ .Values.global.binaryVersion }}&lt;/span&gt;
  &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;sh&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;-c&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;gnoland config set telemetry.enabled true&lt;/span&gt;
    &lt;span class="s"&gt;gnoland config set telemetry.service_instance_id val-01&lt;/span&gt;
    &lt;span class="s"&gt;gnoland config set telemetry.exporter_endpoint otel-svc.svc.cluster.local:4317&lt;/span&gt;
&lt;span class="s"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;We have seen here why and how Open Telemetry was adopted in Gnoland. Moreover we have been able to introduce OTel into the observability stack,&lt;br&gt;
first in a simple Docker Compose and then in Kubernetes.&lt;br&gt;
Finally OpenTelemetry became part of the observability stack in Gnoland!&lt;/p&gt;

</description>
      <category>devops</category>
      <category>web3</category>
      <category>blockchain</category>
    </item>
    <item>
      <title>Built-in min() and max() methods in Go 1.21</title>
      <dc:creator>Sergio Matone</dc:creator>
      <pubDate>Wed, 24 Apr 2024 08:18:59 +0000</pubDate>
      <link>https://dev.to/sw360cab/built-in-min-and-max-methods-in-go-121-24ph</link>
      <guid>https://dev.to/sw360cab/built-in-min-and-max-methods-in-go-121-24ph</guid>
      <description>&lt;p&gt;I recently realized that in &lt;a href="https://go.dev/doc/go1.21" rel="noopener noreferrer"&gt;Go 1.21&lt;/a&gt; the built-in methods &lt;a href="https://go.dev/ref/spec#Min_and_max" rel="noopener noreferrer"&gt;min()&lt;/a&gt; and &lt;a href="https://go.dev/ref/spec#Min_and_max" rel="noopener noreferrer"&gt;max()&lt;/a&gt; where introduced, which allows you to get the min and max of a set of heterogeneous items. This is now doable thanks to the introduction of Generics in &lt;a href="https://go.dev/doc/go1.18" rel="noopener noreferrer"&gt;Go 1.18&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So, for example when I have a string, let's say a title, and I want to get the substring which has a length gathered from the minimum value between a given number and the length of the string itself. Once I would have used the &lt;code&gt;math.Min&lt;/code&gt; method, which requires two &lt;code&gt;float64&lt;/code&gt; types as parameters, and I would have done the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;MAX_TITLE_LENGTH&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;50&lt;/span&gt;

&lt;span class="c"&gt;// a placeholder title&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"..."&lt;/span&gt; &lt;span class="c"&gt;// the given title&lt;/span&gt;

&lt;span class="n"&gt;titleMaxLength&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; 
  &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="kt"&gt;float64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MAX_TITLE_LENGTH&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="kt"&gt;float64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; 
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;croppedTitle&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;titleMaxLength&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whereas now I can achieve the same by simplifying using built-in &lt;code&gt;min()&lt;/code&gt; method, without casting over and over the operands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;titleMaxLength&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;MAX_TITLE_LENGTH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we check the definition of the method, there is another important thing to notice, the method is not limited to a pair of parameters, but it accepts an unlimited number of (homogeneous) parameters.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;builtin&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;min&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;cmp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ordered&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note:&lt;br&gt;
Even though we are already at &lt;a href="https://go.dev/doc/go1.22" rel="noopener noreferrer"&gt;Go 1.22&lt;/a&gt;, I realized a little late that this feature was actually introduced in Go 1.21. What should I say about that? It's better late than never!&lt;/p&gt;

</description>
      <category>go</category>
      <category>coding</category>
      <category>tips</category>
    </item>
    <item>
      <title>Containerizing AOM AV1 and encoding videos with FFmpeg in an Node.js app</title>
      <dc:creator>Sergio Matone</dc:creator>
      <pubDate>Mon, 27 Nov 2023 15:50:33 +0000</pubDate>
      <link>https://dev.to/sw360cab/containerizing-aom-av1-and-encoding-videos-with-ffmpeg-in-an-nodejs-app-1lh2</link>
      <guid>https://dev.to/sw360cab/containerizing-aom-av1-and-encoding-videos-with-ffmpeg-in-an-nodejs-app-1lh2</guid>
      <description>&lt;p&gt;I have recently had the opportunity to work and study the AV1 Codec by AOM (&lt;a href="https://aomedia.org/" rel="noopener noreferrer"&gt;Alliance for Open Media&lt;/a&gt;). It is the state-of-the-art video codec for manipulating videos today, and it is stunning how it is able to compress videos producing files with reduced size and a pretty much untouched quality (I will later do a small comparison with the ancestor AVC/H.264 by MPEG).&lt;br&gt;
And since I am a big, better huge fan of FFmpeg I cannot step back from compiling a fully working FFmpeg binary within a container image. Later I will describe how I achieved that and how it can be used within a Node.js application.&lt;/p&gt;

&lt;p&gt;You can check out the source code in &lt;a href="https://github.com/sw360cab/docker-ffmpeg-av1" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  A bit of history
&lt;/h2&gt;

&lt;p&gt;The creation of the AOM group is a sort of strong answer to the last video codecs standardized by the MPEG group.&lt;br&gt;
Starting from the end of the previous millennium, the standardization of audio and video formats and codecs was dominated by the MPEG group, with the huge success of the MP3 standard (aka MPEG-1 layer 3) for audio, followed by the other great success of the AVC codec for videos, created jointly with ITU.T (which internally nominated the standard H.264 and used to commonly refer to it, instead of AVC).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1zpuoc8i1qc18yox6r21.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1zpuoc8i1qc18yox6r21.jpg" alt="Say H264 one more time." width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The licensing model of codecs by MPEG started to have some critics already with AVC, but it reached its peak with the upcoming HEVC standard (aka H.265) whose licensing model appeared unacceptable for many players of the video &amp;amp; internet industry led by Google.&lt;br&gt;&lt;br&gt;
The result was the creation of the new standardization group Alliance for Open Media (AOM), whose main focus was creating the next optimize royalty-free video codec: AV1.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr2qxo1b9qjpu1yz5o1a8.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr2qxo1b9qjpu1yz5o1a8.jpg" alt="AOM AV1 vs MPEG HEVC" width="500" height="756"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Containerizing AV1 with FFmpeg in a Docker IM
&lt;/h2&gt;

&lt;p&gt;Evaluating the performance of the AV1 codec would be almost impossible without leveraging FFmpeg, the state-of-the-art and reference software to manipulate, compress and low-level edit of videos, the software is also the reference for big Video streaming platforms like Netflix, Disney+ and YouTube.&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1710440696941809868-840" src="https://platform.twitter.com/embed/Tweet.html?id=1710440696941809868"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1710440696941809868-840');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1710440696941809868&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;And creating a portable solution would be not possible as well without building a Container Image in Docker which allows pin versions of libraries and reuse, update and run them without taking care to the current system configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dockerfile for FFmpeg + AV1
&lt;/h2&gt;

&lt;p&gt;To achieve a fully-functional FFmpeg application from Docker, let's start from an Ubuntu Image and then build on top of it all the single codecs and libraries.&lt;br&gt;
In this case it is very useful to deal with a Docker Image since versions of libraries and download url counterparts can be accumulated at the beginning of the Dockerfile and eventually overwritten via build arguments.&lt;/p&gt;

&lt;p&gt;Most libraries during compilation and build, will write information that FFmpeg compilation step will then retrieve using &lt;code&gt;pkg-config&lt;/code&gt;.&lt;br&gt;
The following codecs libraries will be downloaded and compiled statically, each reporting in parenthesis the flag to enable it during FFmpeg &lt;em&gt;building from source&lt;/em&gt; step.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;libfreetype (--enable-libfreetype)&lt;/li&gt;
&lt;li&gt;libx264 (--enable-libx264)&lt;/li&gt;
&lt;li&gt;libx265 (--enable-libx264)&lt;/li&gt;
&lt;li&gt;libfdk-aac (--enable-libfdk-aac)&lt;/li&gt;
&lt;li&gt;liblame-mp3 (--enable-libmp3lame)&lt;/li&gt;
&lt;li&gt;libaom (--enable-libaom)&lt;/li&gt;
&lt;li&gt;libsvtav1 (--enable-libsvtav1)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For AV1 both &lt;code&gt;libaom&lt;/code&gt; and &lt;code&gt;libsvtav1&lt;/code&gt; will be built. However according to FFmpeg documentation about AV1, SVT-AV1 is now the reference for &lt;a href="https://trac.ffmpeg.org/wiki/Encode/AV1#SVT-AV1" rel="noopener noreferrer"&gt;AV1 encoding&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;SVT-AV1 was adopted by AOMedia as the basis for the future development of AV1 as well as future codec efforts.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After all these steps in the Dockerfile, it is possible to proceed with the download and build of Ffmpeg from source code (I picked a pinned version from a git tag: 5.1), enabling with flags the previously compiled codecs. &lt;br&gt;
As for the other libraries compiled in previous steps, the &lt;em&gt;configure, make, make install&lt;/em&gt; will happen in a single &lt;code&gt;RUN&lt;/code&gt; step.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;RUN git clone &lt;span class="nt"&gt;--depth&lt;/span&gt; 1 &lt;span class="nt"&gt;--branch&lt;/span&gt; n5.1 https://git.ffmpeg.org/ffmpeg.git ffmpeg &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;ffmpeg &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nv"&gt;PKG_CONFIG_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BUILD_PREFIX&lt;/span&gt;&lt;span class="s2"&gt;/lib/pkgconfig"&lt;/span&gt; ./configure &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--prefix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BUILD_PREFIX&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--pkg-config-flags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"--static"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--extra-cflags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"-I&lt;/span&gt;&lt;span class="nv"&gt;$BUILD_PREFIX&lt;/span&gt;&lt;span class="s2"&gt;/include"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--extra-ldflags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"-L&lt;/span&gt;&lt;span class="nv"&gt;$BUILD_PREFIX&lt;/span&gt;&lt;span class="s2"&gt;/lib -L&lt;/span&gt;&lt;span class="nv"&gt;$BUILD_PREFIX&lt;/span&gt;&lt;span class="s2"&gt;/lib64"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--extra-libs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"-lpthread -lm -lz"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--extra-ldexeflags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"-static"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--bindir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/bin"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enable-gpl&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--disable-shared&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enable-static&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enable-nonfree&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--disable-libfreetype&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enable-libfdk-aac&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enable-libmp3lame&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enable-libx264&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enable-libx265&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enable-libaom&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enable-libsvtav1&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--disable-doc&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--disable-podpages&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--disable-debug&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  make &lt;span class="nt"&gt;-j&lt;/span&gt; 10 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  make &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Note for SVT-AV1
&lt;/h3&gt;

&lt;p&gt;In order to add the compiled &lt;code&gt;libsvtav1&lt;/code&gt; library to the FFmpeg build, some patches provided by libsvtav1 library itself should be applied to the FFmpeg source code, so an additional &lt;code&gt;RUN&lt;/code&gt; step has been added.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;RUN git clone &lt;span class="nt"&gt;--depth&lt;/span&gt; 1 &lt;span class="nt"&gt;--branch&lt;/span&gt; n5.1 https://git.ffmpeg.org/ffmpeg.git ffmpeg &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  git config &lt;span class="nt"&gt;--global&lt;/span&gt; user.email &lt;span class="s2"&gt;"media@minimalgap.com"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  git &lt;span class="nt"&gt;-C&lt;/span&gt; ffmpeg am &lt;span class="nv"&gt;$BUILD_PREFIX&lt;/span&gt;/ffmpeg_plugin/n5.1/&lt;span class="k"&gt;*&lt;/span&gt;.patch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;output&lt;/code&gt; of this image is a layer with a compiled and working FFmpeg (and FFprobe) binary ready to use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using FFmpeg binary
&lt;/h2&gt;

&lt;p&gt;Now that we have a working Docker image, we can build on top of that other applications that can easily leverage FFmpeg (or its sibling FFprobe) binary.&lt;br&gt;
Again we will take advantage of Docker in particular of &lt;code&gt;multi-stage build&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I've created a basic Node.js application whose purpose is to create a compressed video encoded with AV1 and place it in a specific folder with&lt;br&gt;
&lt;code&gt;.mp4&lt;/code&gt; extension starting from command line and an input video and specifying a codec among AV1, H.264 and HEVC.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Of course this application is a POC and can be expanded with any other possible option.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The Dockerfile for the Node.js will include the FFmpeg binary via the &lt;code&gt;COPY&lt;/code&gt; directive. I have a used a very common pattern that Docker enables using multi stage build: the &lt;code&gt;COPY&lt;/code&gt; directive has a peculiarity clearly stated in the official Dockerfile reference doc:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;COPY accepts a flag --from=&lt;code&gt;&amp;lt;name&amp;gt;&lt;/code&gt; that can be used to set the source location to a previous build stage (created with FROM .. AS &lt;code&gt;&amp;lt;name&amp;gt;&lt;/code&gt;) that will be used instead of a build context sent by the user. In case a build stage with a specified name can't be found an image with the same name is attempted to be used instead.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I have also included a &lt;a href="https://github.com/sw360cab/docker-ffmpeg-av1/blob/master/docker-compose.yml" rel="noopener noreferrer"&gt;docker-compose version&lt;/a&gt; where both the images can be built in a single shot.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gotcha - BUILDKIT
&lt;/h3&gt;

&lt;p&gt;Since &lt;code&gt;BuildKit&lt;/code&gt; is enabled by default in the newer versions of Docker Engine, when building a compose file with multiple services it is not guaranteed that the order of appearance in the compose file is respected because of &lt;code&gt;BuildKit&lt;/code&gt; itself attempting to optimize the build process.&lt;/p&gt;

&lt;p&gt;To overcome this problem it is possible to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Disable BuildKit
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;DOCKER_BUILDKIT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 docker-compose build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Alternatively build &lt;code&gt;ffmpeg&lt;/code&gt; image separately and then the remaining services
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  docker-compose build ffmpeg
  docker-compose build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  AOM AV1 vs MPEG HEVC [aka H.265] vs MPEG AVC [aka H.264]
&lt;/h2&gt;

&lt;p&gt;The application previously created and build is able to encode videos from command line, for sake of simplicity it will allow only two options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;-i&lt;/strong&gt; for input file path&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;-codec&lt;/strong&gt; for video codec to be picked among &lt;em&gt;av1, hevc, h264&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Again to keep it simple each audio will be encoded using &lt;code&gt;HE_AAC&lt;/code&gt; audio codec.&lt;br&gt;&lt;br&gt;
Whereas for each video codec there is an hardcoded configuration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;for &lt;strong&gt;AV1&lt;/strong&gt;: &lt;code&gt;-c:v libsvtav1 -pix_fmt yuv420p -crf 45 -preset 7 -svtav1-params fast-decode=1:film-grain=7:tune=0&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;for &lt;strong&gt;HEVC&lt;/strong&gt;: &lt;code&gt;libx265 -pix_fmt yuv420p -profile:v main -preset slower -crf 27&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;for &lt;strong&gt;H.264&lt;/strong&gt;: &lt;code&gt;libx264 -pix_fmt yuv420p -profile:v high -level 4.1 -preset slower -crf 22 -tune film&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The purpose here is just to provide a quick way to compare results of videos processed with each codec both visually and with respect to file size and CPU time to process a video.&lt;/p&gt;

&lt;h3&gt;
  
  
  Notes on CRF
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Constant Rate Factor&lt;/code&gt; (CRF) is a rate control mode that allows the encoder to attempt to achieve a certain output quality for the whole file when output file size is of less importance.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;for &lt;strong&gt;AV1&lt;/strong&gt; [&lt;em&gt;libsvtav1&lt;/em&gt;]: the valid CRF value range is 0-63, with the default being 50. Lower values correspond to higher quality and greater file size&lt;/li&gt;
&lt;li&gt;for &lt;strong&gt;HEVC&lt;/strong&gt; [&lt;em&gt;libx265&lt;/em&gt;]: the mode works exactly the same as in &lt;code&gt;libx264&lt;/code&gt;, except that maximum value is always 51, even with 10-bit support. The default is 28, and it should visually correspond to &lt;em&gt;libx264&lt;/em&gt; video at CRF 23&lt;/li&gt;
&lt;li&gt;for &lt;strong&gt;H.264&lt;/strong&gt; [&lt;em&gt;libx264&lt;/em&gt;]: The range of the CRF scale is 0–51, where 0 is lossless (for 8 bit only, for 10 bit use -qp 0), 23 is the default, and 51 is worst quality possible&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Making a deep comparison among the results achieved from the 3 different codecs is out of the scope of this post. It will also be unfair, since all the three codecs have plenty of options and it's not easy to achieve three identical and fair configurations to make a real comparisons.&lt;br&gt;
Moreover the results would be strongly influenced by the input video and its codecs configuration.&lt;/p&gt;

&lt;p&gt;I will here highlight the main feedbacks so far:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;AV1&lt;/em&gt; is in general the slower to process but it produces the smallest sized output file.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;H.264/AVC&lt;/em&gt; is the fastest one but it produces the output file with the biggest size.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;HEVC&lt;/em&gt; is as slow as AV1 but it tends to create bigger sized files (this is a guess because it is strongly influenced by codec configuration)&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;AV1&lt;/em&gt; apparently produce amazingly high quality video even if compression is at a very high rate&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Test Video 1: small sized video
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fpracticaldev%2Fimage%2Ffetch%2Fs--B8KRUCiX--%2Fc_limit%252Cf_auto%252Cfl_progressive%252Cq_auto%252Cw_800%2Fhttps%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx2dh2q4z5eaz40ougj9j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fpracticaldev%2Fimage%2Ffetch%2Fs--B8KRUCiX--%2Fc_limit%252Cf_auto%252Cfl_progressive%252Cq_auto%252Cw_800%2Fhttps%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx2dh2q4z5eaz40ougj9j.png" alt="Video 1" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/sw360cab/docker-ffmpeg-av1/raw/master/data/sample_0.mp4" rel="noopener noreferrer"&gt;Source Video&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Video codec: AVC/H264&lt;/li&gt;
&lt;li&gt;Duration: ~ 20 s&lt;/li&gt;
&lt;li&gt;File size: 54 MB&lt;/li&gt;
&lt;li&gt;Ffprobe Output:&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Duration: 00:00:18.88, start: 0.000000, bitrate: 23842 kb/s&lt;br&gt;
Stream #0:0: Video: h264 (High) (avc1 / 0x31637661), yuvj420p(pc, bt709, progressive), 1920x1080, 23673 kb/s, 25 fps, 25 tbr, 25k tbn (default)&lt;br&gt;
Stream #0:1: Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 127 kb/s (default)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;HW: Mac Book Pro 2018, CPU Intel i7 2,6 GHz 6-cores, RAM 32 GB&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Codec&lt;/th&gt;
&lt;th&gt;File Size&lt;/th&gt;
&lt;th&gt;CPU Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AV1&lt;/td&gt;
&lt;td&gt;2.5 MB&lt;/td&gt;
&lt;td&gt;38 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HEVC&lt;/td&gt;
&lt;td&gt;5.7 MB&lt;/td&gt;
&lt;td&gt;464 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;H.264&lt;/td&gt;
&lt;td&gt;13.3 MB&lt;/td&gt;
&lt;td&gt;24 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;HW: Linux Station, CPU Intel Xeon Gold 6230 2,1 GHz 8-cores, RAM 16 GB&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Codec&lt;/th&gt;
&lt;th&gt;File Size&lt;/th&gt;
&lt;th&gt;CPU Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AV1&lt;/td&gt;
&lt;td&gt;2.5 MB&lt;/td&gt;
&lt;td&gt;58 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HEVC&lt;/td&gt;
&lt;td&gt;5.7 MB&lt;/td&gt;
&lt;td&gt;328 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;H.264&lt;/td&gt;
&lt;td&gt;13.3 MB&lt;/td&gt;
&lt;td&gt;40 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Test Video 2: raw video
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fpracticaldev%2Fimage%2Ffetch%2Fs--D59_kYZv--%2Fc_limit%252Cf_auto%252Cfl_progressive%252Cq_auto%252Cw_800%2Fhttps%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5kay0zcwp30gmu8jkmc3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fpracticaldev%2Fimage%2Ffetch%2Fs--D59_kYZv--%2Fc_limit%252Cf_auto%252Cfl_progressive%252Cq_auto%252Cw_800%2Fhttps%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5kay0zcwp30gmu8jkmc3.png" alt="Video 2" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://media.xiph.org/video/derf/ElFuente/Netflix_BoxingPractice_4096x2160_60fps_10bit_420.y4m" rel="noopener noreferrer"&gt;Source Video&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Video codec: N/A - Raw video&lt;/li&gt;
&lt;li&gt;Duration: ~ 4.30 min&lt;/li&gt;
&lt;li&gt;File size: 6.3 GB&lt;/li&gt;
&lt;li&gt;Ffprobe Output:&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Duration: 00:00:04.23, start: 0.000000, bitrate: 12740202 kb/s&lt;br&gt;
Stream #0:0: Video: rawvideo, yuv420p10le(progressive), 4096x2160, SAR 1:1 DAR 256:135, 60 fps, 60 tbr, 60 tbn&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;HW: Mac Book Pro 2018, CPU Intel i7 2,6 GHz 6-cores, RAM 32 GB&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Codec&lt;/th&gt;
&lt;th&gt;File Size&lt;/th&gt;
&lt;th&gt;CPU Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AV1&lt;/td&gt;
&lt;td&gt;3 MB&lt;/td&gt;
&lt;td&gt;136 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HEVC&lt;/td&gt;
&lt;td&gt;4 MB&lt;/td&gt;
&lt;td&gt;780 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;H.264&lt;/td&gt;
&lt;td&gt;15.4 MB&lt;/td&gt;
&lt;td&gt;85 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;HW: Linux Station, CPU Intel Xeon Gold 6230 2,1 GHz 8-cores, RAM 16 GB&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Codec&lt;/th&gt;
&lt;th&gt;File Size&lt;/th&gt;
&lt;th&gt;CPU Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AV1&lt;/td&gt;
&lt;td&gt;3 MB&lt;/td&gt;
&lt;td&gt;159 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HEVC&lt;/td&gt;
&lt;td&gt;4 MB&lt;/td&gt;
&lt;td&gt;625 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;H.264&lt;/td&gt;
&lt;td&gt;15.4 MB&lt;/td&gt;
&lt;td&gt;84 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Recap
&lt;/h2&gt;

&lt;p&gt;As said the purpose of these compression tests is not a deep comparison among codec results and parameters.&lt;br&gt;
From the previous tests I can state that AV1 is giving back strongly compressed videos with a long processing duration compared to H.264. It is also giving similar results to HEVC but with a faster compression time with and comparable or smaller sized videos.&lt;br&gt;
Compared to H.264/AVC, the codec AV1 is taking an inversely proportional CPU time compared to order of magnitude in terms of reduced size.&lt;/p&gt;

&lt;p&gt;Check out the source code in &lt;a href="https://github.com/sw360cab/docker-ffmpeg-av1" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/sw360cab/docker-ffmpeg-av1" rel="noopener noreferrer"&gt;sw360cab/docker-ffmpeg-av1: Containerized fully static compiled FFmpeg binary with AOM AV1 Codec&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://trac.ffmpeg.org/wiki/Encode/H.264" rel="noopener noreferrer"&gt;Encode/H.264 – FFmpeg&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://trac.ffmpeg.org/wiki/Encode/H.265" rel="noopener noreferrer"&gt;Encode/H.265 – FFmpeg&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://trac.ffmpeg.org/wiki/Encode/AV1#SVT-AV1" rel="noopener noreferrer"&gt;Encode/AV1 STV-AV1 – FFmpeg&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aomedia.org/av1/" rel="noopener noreferrer"&gt;AV1 Video Codec | Alliance for Open Media&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://media.xiph.org/video/derf/" rel="noopener noreferrer"&gt;Xiph.org :: Derf's Test Media Collection&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ffmpeg</category>
      <category>av1</category>
      <category>docker</category>
      <category>codecs</category>
    </item>
    <item>
      <title>Handling Rate Limits in GetStream API Calls in Go leveraging Channels and Goroutines</title>
      <dc:creator>Sergio Matone</dc:creator>
      <pubDate>Tue, 10 Oct 2023 11:10:59 +0000</pubDate>
      <link>https://dev.to/sw360cab/handling-rate-limits-in-getstream-api-calls-in-go-leveraging-channels-and-goroutines-29a4</link>
      <guid>https://dev.to/sw360cab/handling-rate-limits-in-getstream-api-calls-in-go-leveraging-channels-and-goroutines-29a4</guid>
      <description>&lt;p&gt;TL; DR&lt;/p&gt;

&lt;p&gt;This is my own solution to overcome rate limits for GetStream's API Endpoints when employing Go SDK.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://getstream.io/" rel="noopener noreferrer"&gt;GetStream&lt;/a&gt; is a great SaaS offering Chat &amp;amp; Feed services and a wide variety of SDKs acting as clients to handle those items and their users as well.&lt;br&gt;
Speaking of chats in GetStream, I faced a severe problem in one of the projects I worked on: &lt;a href="https://getstream.io/chat/docs/go-golang/rate_limits/?language=go" rel="noopener noreferrer"&gt;Rate Limits in API calls&lt;/a&gt;.&lt;br&gt;
Indeed GetStream requires the various clients to perform their requests respecting a specific rate limiting in requesting the endpoints available, such as &lt;code&gt;QueryChannels&lt;/code&gt;, &lt;code&gt;QueryUsers&lt;/code&gt;, &lt;code&gt;CreateChannel&lt;/code&gt;. Limits for the different endpoints are listed &lt;a href="https://getstream.io/chat/docs/go-golang/rate_limits_table/?language=go" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcr7ofw74labd1lb5d31b.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcr7ofw74labd1lb5d31b.jpeg" alt="Am I crying?" width="360" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is not easy to deal with these boundaries, especially when you create batch tasks, because we tend to imagine and code big &lt;code&gt;loops&lt;/code&gt; to apply multiple requests to a set of homogeneous data.&lt;br&gt;
In my case, in  the startup I was working for, we had a task to handle a large set of groups, each corresponding to a chat group in GetStream; participants in these groups used to be added or removed by a sort of group's administrator. Since this might not be reflected real-time in the chat groups, there was a nightly task that basically checks for each user whether he should be added to or removed from a chat group.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Probably watching the problem upside down, it would be more clever to check all the existing chat groups instead of every user, but this is not the point here. Let's suppose this is the best implementation we could use.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sometimes the task was limited to a small bunch of changes, but most of the time many users were repeatedly added and removed from groups. And that made the task failing often because after several requests it started to time out over and over for all the following ones.&lt;/p&gt;

&lt;p&gt;In order to find a solution there are a couple things to take into account:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Get Stream is setting in the response the &lt;a href="https://getstream.io/chat/docs/go-golang/rate_limits/?language=go#rate-limit-headers" rel="noopener noreferrer"&gt;headers&lt;/a&gt; that specify for each kind of api (remember that limits are tied to each specific endpoint): which are the defined limits (&lt;code&gt;X-RateLimit-Limit&lt;/code&gt;), how many requests are still available (&lt;code&gt;X-RateLimit-Remaining&lt;/code&gt;) and when eventually the block for hitting rate limit will be reset (&lt;code&gt;X-RateLimit-Timestamp&lt;/code&gt;) in Unix timestamp format.&lt;/li&gt;
&lt;li&gt;The task needs a way to distinguish among each of all the (employed) Api endpoints, whether the timeout has been reached, and in case it is, a way to hold all the following requests for a given time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of that makes me think about generating a sort of barrier for the Api requests that will arrive after the rate limit for the given endpoint is hit. There should be multiple barriers, one for each endpoint and they should be able to block following requests for a specific amount of time given. I named that &lt;code&gt;timed-barrier&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcigaohcx6hf3xa0xbky0.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcigaohcx6hf3xa0xbky0.jpg" alt="Let the pain begin" width="375" height="210"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Being inside a Go environment I guessed that &lt;strong&gt;Channels&lt;/strong&gt; and &lt;strong&gt;Goroutines&lt;/strong&gt; would be a natural means to deal with the issue. I achieved distinguishing among the different Api endpoints creating a Timed-barrier reference struct.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;RateLimiter&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;apiName&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
  &lt;span class="n"&gt;token&lt;/span&gt;   &lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;RateLimiter&lt;/code&gt; struct represents the Timed-barrier having an Api Endpoint reference and a buffered channel with capacity 1.&lt;/p&gt;

&lt;p&gt;☠️ Initially I thought about an unbuffered channel. Since the scenario is not aiming to coordinate two Goroutines against each other but a group of them, it would have created an immediate deadlock.&lt;/p&gt;

&lt;p&gt;The buffered channel will be the trick to overcome the rate limits issue. Indeed the first call will occupy the single slot available in the channel and it will release it after calling the API, depending on the return value of current rate limits. If the value of the remaining calls is greater than zero, it will release the channel's slot immediately, otherwise it will wait until the timeout has expired.&lt;/p&gt;

&lt;p&gt;But let see this in details by analyzing the RateLimiter's method &lt;code&gt;CallApiAndBlockOnRateLimit&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;RateLimiter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;CallApiAndBlockOnRateLimit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;apiCall&lt;/span&gt; &lt;span class="n"&gt;GetStreamApiCaller&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}{}&lt;/span&gt;
  &lt;span class="c"&gt;// Injected api call&lt;/span&gt;
  &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;apiCall&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RateLimitInfo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Remaining&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;duration&lt;/span&gt; &lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sleep&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;duration&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unix&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Abs&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;
    &lt;span class="p"&gt;}(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RateLimitInfo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Reset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// &amp;lt;-- when the current limit will reset (Unix timestamp in seconds)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first step is occupying the single slot in the buffered channel. It means that any subsequent call will be blocked waiting for an available slot in the channel.&lt;br&gt;
The Api endpoint is called leveraging a clojure or higher-order function type, such as&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;GetStreamApiCaller&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;that is basically supposed to call a given GetStream endpoint, which would place the response in a specific type, according to the Api called. The role of the function is extracting a general data structure from the specific response type.&lt;br&gt;
That general response type is &lt;code&gt;stream.Response&lt;/code&gt; which is defined as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;RateLimitInfo&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;RateLimitInfo&lt;/span&gt; &lt;span class="s"&gt;`json:"ratelimit"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;RateLimitInfo&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;// Limit is the maximum number of API calls for a single time window (1 minute).&lt;/span&gt;
  &lt;span class="n"&gt;Limit&lt;/span&gt; &lt;span class="kt"&gt;int64&lt;/span&gt; &lt;span class="s"&gt;`json:"limit"`&lt;/span&gt;
  &lt;span class="c"&gt;// Remaining is the number of API calls remaining in the current time window (1 minute).&lt;/span&gt;
  &lt;span class="n"&gt;Remaining&lt;/span&gt; &lt;span class="kt"&gt;int64&lt;/span&gt; &lt;span class="s"&gt;`json:"remaining"`&lt;/span&gt;
  &lt;span class="c"&gt;// Reset is the Unix timestamp of the expiration of the current rate limit time window.&lt;/span&gt;
  &lt;span class="n"&gt;Reset&lt;/span&gt; &lt;span class="kt"&gt;int64&lt;/span&gt; &lt;span class="s"&gt;`json:"reset"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is clear that this is exactly the key of the method implementation. &lt;br&gt;
The &lt;code&gt;RateLimitInfo&lt;/code&gt; contains all the useful information to proceed.&lt;br&gt;
The &lt;code&gt;Remaining&lt;/code&gt; field will inform if any call is still available for the given Api endpoint. If it is, the channel is freed immediately, otherwise the channel will remain occupied until a specific time is passed. This is achieved leveraging the &lt;code&gt;Reset&lt;/code&gt; field which will hold the slot within a Goroutine, until the given time (duration given as Unix timestamp in seconds) has passed, by just &lt;em&gt;sleeping&lt;/em&gt;.&lt;br&gt;
Moreover even if in a group of homogeneous and distinguished calls order may not be important, blocking all the subsequent calls waiting for the slot of the channel to be empty, will guarantee consistency in the order in which they will be called after the channel slot is released.&lt;/p&gt;

&lt;p&gt;All you have to do is create a distinct &lt;code&gt;RateLimiter&lt;/code&gt; object for each kind of Api Endpoint in GetStream.&lt;/p&gt;
&lt;h2&gt;
  
  
  Library in action
&lt;/h2&gt;

&lt;p&gt;In a real world scenario, given a &lt;code&gt;GetStream&lt;/code&gt; chat client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;  &lt;span class="n"&gt;getStreamChatClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&amp;lt;API_KEY&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"&amp;lt;API_SECRET&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a distinct set of RateLimiter objects, collected in a map having as key type a GetStream Api Endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;GetStreamApiName&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;CreateChannel&lt;/span&gt; &lt;span class="n"&gt;GetStreamApiName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"CreateChannel"&lt;/span&gt;
  &lt;span class="n"&gt;QueryChannel&lt;/span&gt;  &lt;span class="n"&gt;GetStreamApiName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"QueryChannel"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;rateLimiterMap&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;GetStreamApiName&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;RateLimiter&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;CreateChannel&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;apiName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CreateChannel&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="n"&gt;QueryChannel&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;apiName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CreateChannel&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A method to call GetStream &lt;code&gt;QueryChannel&lt;/code&gt; Api leveraging &lt;code&gt;RateLimiter&lt;/code&gt; can be defined as following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;queryChannels&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filters&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queryResp&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;QueryChannelsResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;rateLimiter&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;rateLimiterMap&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"QueryChannel"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;errCall&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;rateLimiter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CallApiAndBlockOnRateLimit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;queryResp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getStreamChatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;QueryChannels&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;QueryOption&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Filter&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;queryResp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="n"&gt;errCall&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;errCall&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;queryResp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🚀 That's it! I hope you like the way I faced the issue. Feel free to comment or propose your own idea to deal with this.&lt;/p&gt;

&lt;p&gt;⚔️ In case anyone finds it useful I packed all of the above together in a &lt;a href="https://github.com/sw360cab/getstream-rate-limiter" rel="noopener noreferrer"&gt;Github repo&lt;/a&gt;.&lt;br&gt;
You should be able to use it also as module in your code using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go get &lt;span class="nt"&gt;-u&lt;/span&gt; github.com/sw360cab/getstream-rate-limiter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>go</category>
      <category>getstream</category>
      <category>lib</category>
    </item>
    <item>
      <title>Migrating AWS SDK from v2 to v3 for S3</title>
      <dc:creator>Sergio Matone</dc:creator>
      <pubDate>Tue, 03 Oct 2023 09:07:19 +0000</pubDate>
      <link>https://dev.to/sw360cab/migrating-aws-sdk-from-v2-to-v3-for-s3-32lh</link>
      <guid>https://dev.to/sw360cab/migrating-aws-sdk-from-v2-to-v3-for-s3-32lh</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Disclaimer: Sample of code described here can be found at &lt;a href="https://github.com/sw360cab/aws-sdk-migration-v3" rel="noopener noreferrer"&gt;sw360cab/aws-sdk-migration-v3&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Short time ago, I found myself in the very rare situation where you can dedicate some time slots to library upgrades in any of your projects. The purists will blame me for this “rare” adjective and bad practice, but even with all the good intentions, to me it is not so common to reserve time for this activity.&lt;br&gt;
This makes me often feel like a lazy and bad software engineer, but breaking changes usually kills me.&lt;br&gt;
And the following story will make no exception.&lt;/p&gt;

&lt;p&gt;As I said I was starting upgrading libraries in a Node.js Git repository related to a project of &lt;a href="https://www.qua-app.com/en/" rel="noopener noreferrer"&gt;QUA&lt;/a&gt;, the startup I own and run.&lt;br&gt;
I started running the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm outdated
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and after a small check I ran:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I achieved a consistent result. Spoiler: I can state that because I have a solid unit/integration test base.&lt;br&gt;
But then I noticed a warning:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;NOTE: We are formalizing our plans to enter AWS SDK for JavaScript (v2) into maintenance mode in 2023.&lt;br&gt;
  Please migrate your code to use AWS SDK for JavaScript (v3).&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After digging into my code and googling a little bit, I found out that I was still using AWS SDK v2 throughout all the code. After a moment to recover from the loathing of this news, I branched and started to imagine how painful this journey would have been.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: AWS provides an automatic tool to migrate code from v2 to v3: &lt;a href="https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/migrating-to-v3.html" rel="noopener noreferrer"&gt;Migrating your code to SDK for JavaScript V3 - AWS SDK for JavaScript&lt;/a&gt;. Of course I preferred the hard and painful way.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It turns out that it wasn't much to migrate. I will go through the changes soon, but the key breaking changes are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SDK library is now split into dedicated libraries instead of importing the whole SDK and then initializing the required service.&lt;/li&gt;
&lt;li&gt;Configuration can be done from ini files or local AWS CLI configuration.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Migration in details
&lt;/h2&gt;

&lt;p&gt;Let's go.&lt;br&gt;
The main service I leverage in my codebase are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS S3&lt;/li&gt;
&lt;li&gt;AWS SES&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;First step was adjust libraries version in &lt;code&gt;package.json&lt;/code&gt; by uninstalling current AWS SDK&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm uninstall aws-sdk &lt;span class="nt"&gt;--save&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;AWS Credentials setup
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i @aws-sdk/credential-providers &lt;span class="nt"&gt;--save&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;S3
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i @aws-sdk/client-s3 @aws-sdk/lib-storage &lt;span class="nt"&gt;--save&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;SES
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i @aws-sdk/client-ses &lt;span class="nt"&gt;--save&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Credentials Setup
&lt;/h3&gt;

&lt;p&gt;In the codebase using v2 of SDK I used to setup SDK credentials either using environment variables or via a JSON config file (which was not committed 😇)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;initAws&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// if env variables are not provided&lt;/span&gt;
  &lt;span class="c1"&gt;// fallback to hardcoded static file&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;
  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;AWS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadFromPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;awsConfigPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`AWS config file not found at &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;awsConfigPath&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unfortunately with v3 loading credentials using a JSON file is not supported anymore, though it is still valid using environment variables.&lt;br&gt;
However the closest way to achieve setup via external configuration files is using the library method &lt;code&gt;fromIni&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;fromIni&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-sdk/credential-providers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This method creates an &lt;code&gt;AwsCredentialIdentityProvider&lt;/code&gt; that read&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a shared credentials file at &lt;code&gt;~/.aws/credentials&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;a shared configuration file at &lt;code&gt;~/.aws/config&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both files are expected to be INI formatted with section names corresponding to profiles.&lt;br&gt;
I have shared an example of an AWS config file since it does not contain any sensible data.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;secrets/aws/config&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;default]
&lt;span class="nv"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;eu-west-1
&lt;span class="nv"&gt;output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whereas credentials file will include sensible information, again in &lt;code&gt;ini&lt;/code&gt; format&lt;/p&gt;

&lt;p&gt;&lt;code&gt;secrets/aws/credentials&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;default]
&lt;span class="nv"&gt;aws_access_key_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;YOUR_ACCESS_KEY_ID&amp;gt;
&lt;span class="nv"&gt;aws_secret_access_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;YOUR_SECRET_ACCESS_KEY&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;NOTE: the line &lt;code&gt;[default]&lt;/code&gt; is referred to the &amp;lt;default&amp;gt; profile, the ini files can contain more than one profile, all in the format &lt;code&gt;[&amp;lt;profile name&amp;gt;]&lt;/code&gt;, but the profile to be employed should be reflected in the AWS SDK configuration.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The default reference path can be overridden using environment variables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS_SHARED_CREDENTIALS_FILE for credentials&lt;/li&gt;
&lt;li&gt;AWS_CONFIG_FILE for config
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;awsCredentialsPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_SHARED_CREDENTIALS_FILE&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/../secrets/aws/credentials&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;awsConfigPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_CONFIG_FILE&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/../secrets/aws/config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;fromIni&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;filepath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;awsCredentialsPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;configFilepath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;awsConfigPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The initialization of a service, like AWS SES, will be changed,&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;from v2
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;AWS&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;AWS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadFromPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;awsConfigPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;AWS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SES&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;to v3
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;fromIni&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-sdk/credential-providers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SESClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-sdk/client-ses&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;awsConfigEnv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fromIni&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;filepath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;awsCredentialsPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;configFilepath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;awsConfigPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SESClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;eu-west-1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;awsConfigEnv&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  S3
&lt;/h3&gt;

&lt;p&gt;The main AWS service I leverage in my codebase is S3 to send/retrieve data to/from AWS S3 buckets.&lt;/p&gt;

&lt;p&gt;Couple remarks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;in v2 to achieve a promise from a client method, a further &lt;code&gt;promise()&lt;/code&gt; method should be called. In v3 the client methods tend to return natively Promises.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In the following example I used native Node.js &lt;code&gt;Passthrough&lt;/code&gt; stream. It is almost useless when using a file read stream directly, but I left it as a reminder because it is common in most use cases, especially when files will be uploaded through an HTTP POST.&lt;/p&gt;

&lt;p&gt;⚠️ &lt;em&gt;These HTTP requests in Node.js are not pure readable streams but usually an object inheriting from the Node.js &lt;code&gt;stream&lt;/code&gt; class, implementing methods like &lt;code&gt;write&lt;/code&gt;. Passthrough streams allow passing data coming from an HTTP connection directly to the bucket without having to store them in local memory, meanwhile.&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  S3 Upload
&lt;/h4&gt;

&lt;p&gt;In v2 I used to upload an “object” into a bucket like that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;AWS&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;uploadToS3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;s3Stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PassThrough&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="c1"&gt;// pipe file and s3 stream to upload&lt;/span&gt;
  &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createReadStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;highWaterMark&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s3Stream&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;S3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upload&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;s3Stream&lt;/span&gt;
  &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;promise&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unable to upload&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now in v3 I have to use the brand new method upload:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;S3Client&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-sdk/client-s3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Upload&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-sdk/lib-storage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;uploadToS3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;s3Stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PassThrough&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="c1"&gt;// pipe file and s3 stream to upload&lt;/span&gt;
  &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createReadStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;highWaterMark&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s3Stream&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;upload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Upload&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;S3Client&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;awsConfigEnv&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt;&lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;s3Stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;upload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unable to upload&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;In v3 the upload part is lead by a dedicated SDK library: &lt;code&gt;lib-storage&lt;/code&gt;, which provides an &lt;code&gt;Upload&lt;/code&gt; class. Its constructor returns an &lt;code&gt;EventEmitter&lt;/code&gt; whereas the &lt;code&gt;done()&lt;/code&gt; method returns a &lt;em&gt;Promise&lt;/em&gt; which successfully resolves when the upload to S3 bucket has finished.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  S3 Download
&lt;/h4&gt;

&lt;p&gt;v2:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;AWS&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Readable&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:stream&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;basicDownload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;destPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;S3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;promise&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;download_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Readable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;download_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createWriteStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;destPath&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unable to download&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now in v3 :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;S3Client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;GetObjectCommand&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-sdk/client-s3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;basicDownload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;destPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;S3Client&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;awsConfigEnv&lt;/span&gt;&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GetObjectCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;download_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;download_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createWriteStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;destPath&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unable to download&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;In v2 &lt;code&gt;Readable&lt;/code&gt; was needed to create a stream from the buffer returned by AWS SDK. Again a Node.js stream can be useful to pipe directly an HTTP response&lt;/li&gt;
&lt;li&gt;In v3 the response from AWS SDK contains directly a Node.js Stream in the Body field.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/modules/_aws_sdk_credential_providers.html#fromtemporarycredentials" rel="noopener noreferrer"&gt;@aws-sdk/credential-providers | AWS SDK for JavaScript v3&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/migrating-to-v3.html" rel="noopener noreferrer"&gt;Migrating your code to SDK for JavaScript V3 - AWS SDK for JavaScript&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/welcome.html" rel="noopener noreferrer"&gt;What's the AWS SDK for JavaScript? - AWS SDK for JavaScript&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>s3</category>
      <category>node</category>
    </item>
    <item>
      <title>Functional Programming: an accumulator of methods to transform objects in Javascript</title>
      <dc:creator>Sergio Matone</dc:creator>
      <pubDate>Tue, 26 Sep 2023 10:09:15 +0000</pubDate>
      <link>https://dev.to/sw360cab/functional-programming-an-accumulator-of-methods-to-transform-objects-in-javascript-3in3</link>
      <guid>https://dev.to/sw360cab/functional-programming-an-accumulator-of-methods-to-transform-objects-in-javascript-3in3</guid>
      <description>&lt;p&gt;While I was trying to solve a problem within my code, I had to modify an object by applying a set of transforming methods. I had to do the same with a whole array of strings, all requesting the same set of transformers to be applied.&lt;/p&gt;

&lt;p&gt;Since I am a big fan of Functional Programming I wanted to use a &lt;code&gt;map-reduce&lt;/code&gt; approach. I came up with a generic method leveraging the native &lt;code&gt;reduce&lt;/code&gt; method of &lt;code&gt;Array&lt;/code&gt; objects, the idea is to “accumulate” a string which at each iteration becomes both the result of the last transformation method and the input for the next transformation method.&lt;/p&gt;

&lt;p&gt;Here is a placeholder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tranformers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;()];&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;multiTransform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;tranformers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;accumulator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;currentValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;currentValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;accumulator&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;  &lt;span class="c1"&gt;// reduce callback&lt;/span&gt;
  &lt;span class="nx"&gt;str&lt;/span&gt; &lt;span class="c1"&gt;// initial value&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Expecting the value 'HELLO WORLD' from `multiTransform` method:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;multiTransform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; hello world &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And in order to show how it can be applied to a set of strings, I will leverage the brand new &lt;a href="https://nodejs.org/api/test.html" rel="noopener noreferrer"&gt;Node.js Native Test Runner&lt;/a&gt;, which is still experimental but can be used from versions of Node.js &amp;gt;= v18.0.0 LTS (or either v16.17.0):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;assert&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:assert&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;array transformation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;arr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; alice &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bob&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; paul&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deepEqual&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ALICE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;BOB&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PAUL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;multiTransform&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="o"&gt;---&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;✔&lt;/span&gt; &lt;span class="nx"&gt;array&lt;/span&gt; &lt;span class="nf"&gt;transformation &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.886&lt;/span&gt;&lt;span class="nx"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In vanilla Javascript, soon everything can become a mess due to the nature of the language, which is weakly typed. So the idea behind this &lt;em&gt;accumulator pattern&lt;/em&gt; is to apply a certain number of transformations to a set of homogeneous objects; &lt;code&gt;Strings&lt;/code&gt; are the most straightforward example of course. &lt;br&gt;
Another possibility is that transformation methods include the capability to receive different object types and transform them according to the type of the object itself.&lt;/p&gt;

</description>
      <category>functional</category>
      <category>javascript</category>
      <category>node</category>
    </item>
    <item>
      <title>Unreachable static website distributed via AWS CloudFront</title>
      <dc:creator>Sergio Matone</dc:creator>
      <pubDate>Wed, 13 Sep 2023 10:07:11 +0000</pubDate>
      <link>https://dev.to/sw360cab/unreachable-static-website-distributed-via-aws-cloudfront-28o3</link>
      <guid>https://dev.to/sw360cab/unreachable-static-website-distributed-via-aws-cloudfront-28o3</guid>
      <description>&lt;p&gt;&lt;strong&gt;DISCLAMER&lt;/strong&gt;: The following was a very specific situation I find myself in when my own website hosted in an AWS S3 bucket and distributed via AWS CloudFront, stopped working. Here is how I solved the issue smoothly.&lt;/p&gt;




&lt;p&gt;I own a very simple personal website and I used to host it in an AWS bucket.&lt;/p&gt;

&lt;p&gt;👉 This is it, &lt;a href="https://cloud.minimalgap.com/" rel="noopener noreferrer"&gt;Minimal Gap&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Distributing using my own domain just required to enable &lt;code&gt;Static website hosting&lt;/code&gt; in AWS S3 Bucket properties and play a little bit with dns.&lt;/p&gt;

&lt;p&gt;Here is a quick summary of how it was done.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;S3 Bucket Name: minimalgap.com&lt;/li&gt;
&lt;li&gt;DNS setup: cloud   CNAME  minimalgap.com.s3-website-eu-west-1.amazonaws.com&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As a further step when I put it online, I decided to distribute it using a CDN, of course the easiest step was to leverage AWS CloudFront.&lt;/p&gt;

&lt;p&gt;To do that I created an AWS distribution, I left most of the options as they are, just changed the following properties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Origin Settings&lt;/code&gt;, for &lt;code&gt;Origin Domain Name&lt;/code&gt;, enter the bucket website endpoint. You get this from the &lt;code&gt;Static website hosting&lt;/code&gt; section of &lt;code&gt;Properties&lt;/code&gt; for the Amazon S3 bucket. This will result in &lt;code&gt;Origin Type&lt;/code&gt; being &lt;code&gt;S3 static website&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;I optionally generated a TLS certificate via AWS, to hold a certificate related to my own domain (minimalgap.com).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of the above is well documented &lt;a href="https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/getting-started-cloudfront-overview.html#getting-started-cloudfront-distribution-root" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, out of nowhere, my CloudFront Distribution stopped working. After a lot of digging I found out that some AWS configurations may have changed internally and my distribution was lacking of  “something”, that prevent it to work correctly.&lt;/p&gt;

&lt;p&gt;It was an issue with the Origin configuration itself. Indeed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;picking my own distribution in AWS Cloud Front and clicking on its ID,&lt;/li&gt;
&lt;li&gt;getting to the view &lt;code&gt;Origins&lt;/code&gt;, I found the correct configuration pointing to my S3 bucket,&lt;/li&gt;
&lt;li&gt;trying to select and edit the problem was clear: the protocol selection was missing (HTTP, HTTPS, Match viewer).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once I selected one of the values the distribution was back working as expected without any issue (see image below).&lt;br&gt;
Therefore that &lt;code&gt;something&lt;/code&gt; was actually the protocol selection within the Origin configuration!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe3kc6bp82sht2j15zape.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe3kc6bp82sht2j15zape.png" alt="Origin settings" width="800" height="633"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As I said my idea is that AWS changed a little bit some configuration so distributions which were already setup, need to be manually fixed. &lt;br&gt;
Maybe this will result useful for someone else encountering the same issue.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>s3</category>
      <category>cloudfront</category>
      <category>website</category>
    </item>
    <item>
      <title>Go: Using reflection to create a generic lookup method</title>
      <dc:creator>Sergio Matone</dc:creator>
      <pubDate>Thu, 17 Mar 2022 12:14:25 +0000</pubDate>
      <link>https://dev.to/sw360cab/go-using-reflection-to-create-a-generic-lookup-method-161</link>
      <guid>https://dev.to/sw360cab/go-using-reflection-to-create-a-generic-lookup-method-161</guid>
      <description>&lt;h2&gt;
  
  
  Starting the journey in GoLang
&lt;/h2&gt;

&lt;p&gt;As a newbie GoLang developer I had to deal with a set of new statements to be understood and digested. I also had to face a new way of thinking that shifted me from what I am used to in JS / TS / NodeJS.\&lt;br&gt;
One aspect which was really tough at the beginning was &lt;code&gt;interfaces&lt;/code&gt;. In my developer experience, I had already encountered interfaces in Java and I didn't like them too much. This wasn't a good way to start with them in Go.\&lt;br&gt;
Then I realized how powerful and useful they could be while creating a whole Go application or library (and they are probably too in Java, no offense).&lt;/p&gt;

&lt;p&gt;The other thing that gave me kind of bad time was the lack of Generics, which luckily has been recently filled in &lt;a href="https://tip.golang.org/doc/go1.18#generics" rel="noopener noreferrer"&gt;Go 1.18&lt;/a&gt;.&lt;br&gt;
The latter was even more annoying when I dealt with a Go project by one of my clients, which is based on Go 1.16 and cannot be upgraded because of some dependency requirements.&lt;/p&gt;

&lt;p&gt;Go is sometimes very verbose, and this can be either good or bad, depending on the situation. For example in that codebase I found a package named &lt;code&gt;arrays&lt;/code&gt; which was taking care of looking up a specific element within a given array/slice.&lt;br&gt;
Why a package? Because they could not find a way to achieve the &lt;code&gt;lookup in array&lt;/code&gt; methods without creating each for any &lt;code&gt;type&lt;/code&gt; that is needed. So any type that needs this lookup method, either built-in (string, int, int64) or custom (struct), should have its own method doing pretty much the same as all the others do.&lt;/p&gt;

&lt;p&gt;When I see too many repetitions and copy/paste of code I smell a rat and I like to remove all the repetitions as soon as possible. Using code repetitions is not only evil, but when you find a bug in a part of the code they may have been repeated tons of times.\&lt;br&gt;
What I wanted is a generic method that can lookup for a value in a slice only if they both hold the same &lt;code&gt;type&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Hands-on
&lt;/h2&gt;

&lt;p&gt;Creating a generic method was pretty simple as changing the parameters from a given type&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;ContainsInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to the most generic one in Go: interface{} also known as &lt;code&gt;any interface&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;containsAny&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But for the body of the method it is another story, even if its original version was pretty trivial:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;ContainsInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;   &lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;      &lt;/span&gt; &lt;span class="err"&gt;   &lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
&lt;span class="err"&gt;      &lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The best way to deal with this is the &lt;code&gt;reflect&lt;/code&gt; package which holds all the methods to reflect, meant as analyzing at its deepest level, a data structure.&lt;/p&gt;

&lt;p&gt;The first problem is that using an &lt;code&gt;any interface&lt;/code&gt; type would lose the explicit type check at compile time, which is one of the best things when writing Go code (but I'll tell you more on that later). This is even worse because the expected parameters should be: a slice and a "same-type" single value.&lt;/p&gt;

&lt;p&gt;First thing to do is checking that the first parameter holds effectively a slice data structure. This can be achieved with the &lt;code&gt;Kind&lt;/code&gt; method of the &lt;code&gt;reflect&lt;/code&gt; package.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;itemsValue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Kind&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;reflect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Slice&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once that we have been assured that the first type is effectively a slice, comparing each of its values with the given value is relatively easy. Indeed the &lt;code&gt;DeepEqual&lt;/code&gt; method allows to compare two values and as the &lt;a href="https://pkg.go.dev/reflect#DeepEqual" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt; states:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Two values of type Interface are deeply equal if they hold deeply equal concrete values.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So for each item in the slice the &lt;code&gt;Interface&lt;/code&gt; method is called, which returns the current value as an &lt;code&gt;interface{}&lt;/code&gt; and that can be deeply compared with the given value, which is already of type &lt;code&gt;interface{}&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;itemsValue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Len&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;      &lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;reflect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DeepEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;itemsValue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Interface&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;   &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
&lt;span class="err"&gt;      &lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Moreover we don't even need &lt;code&gt;type&lt;/code&gt; checking, because the previous method will yield implicitly &lt;code&gt;false&lt;/code&gt; if the compared values are not of the same type. Again the official documentation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Values of distinct types are never deeply equal.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is the complete method statement, with a couple of debugging logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;containsAny&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="n"&gt;itemsValue&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;reflect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ValueOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;itemsValue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Kind&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;reflect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Slice&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;      &lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;itemsValue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Len&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;      &lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;reflect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DeepEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;itemsValue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Interface&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c"&gt;// value, short for -&amp;gt; reflect.Value(value).Interface()&lt;/span&gt;
&lt;span class="err"&gt;      &lt;/span&gt; &lt;span class="err"&gt;   &lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%s and %s &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;itemsValue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;reflect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ValueOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="err"&gt;      &lt;/span&gt; &lt;span class="err"&gt;   &lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%v and %v &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;itemsValue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;reflect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ValueOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="err"&gt;      &lt;/span&gt; &lt;span class="err"&gt;   &lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
&lt;span class="err"&gt;      &lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Too much generic
&lt;/h2&gt;

&lt;p&gt;As I wrote earlier now that everything is so generic I lost some benefits of type checking in Go. There will be no way, except at runtime, to know if the method has been called using the correct types, since interface{} can hold anything at compile time.&lt;/p&gt;

&lt;p&gt;This led me to reuse the previous method declarations with explicit types from the original package but I replaced their body with a simple call to the generic method.&lt;/p&gt;

&lt;p&gt;Several benefits come from that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;exposed methods have a specific type declaration that is checked at compile time;&lt;/li&gt;
&lt;li&gt;generic method can be unexposed by the package since any call to it will happen within the package;&lt;/li&gt;
&lt;li&gt;there is only a source of truth (or of failure) which is the generic method itself;&lt;/li&gt;
&lt;li&gt;the package can be extended to an unlimited number of types only by creating a new public method declaration with the new given types, and it will need to call the sole generic method in it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can find the full code with a bunch of tests &lt;a href="https://github.com/sw360cab/go-reflect-compare" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;After starting learning Go almost one year ago and joining my first real and &lt;code&gt;in-production&lt;/code&gt; project, all based on a GoLang codebase, I am satisfied with having faced these kinds of challenges and having achieved the next level skills.\&lt;br&gt;
Probably Go &lt;em&gt;gurus&lt;/em&gt; will disagree with the solution I came up with, but I will be happy to read their wise advice. I know that for sure this was more an intellectual exercise, since with Go 1.18 all of that may be overtaken by built-in &lt;code&gt;Generics&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>go</category>
      <category>opensource</category>
      <category>reflect</category>
    </item>
    <item>
      <title>Traefik 2.5 - What a Mesh!</title>
      <dc:creator>Sergio Matone</dc:creator>
      <pubDate>Mon, 11 Oct 2021 09:19:48 +0000</pubDate>
      <link>https://dev.to/sw360cab/traefik-2-5-what-a-mesh-dn5</link>
      <guid>https://dev.to/sw360cab/traefik-2-5-what-a-mesh-dn5</guid>
      <description>&lt;p&gt;&lt;strong&gt;Disclaimer&lt;/strong&gt;: This post is freely inspired by an Official Traefik Labs blog &lt;a href="https://traefik.io/blog/integrating-consul-connect-service-mesh-with-traefik-2-5/" rel="noopener noreferrer"&gt;post&lt;/a&gt;.&lt;br&gt;
I took some part and adapted to achieve a simpler working example (in my humble opinion).&lt;/p&gt;

&lt;p&gt;Few weeks ago Traefik Labs announced the new release of Traefik Proxy, now at version 2.5.&lt;br&gt;
Many features have been shipped:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTTP/3&lt;/li&gt;
&lt;li&gt;Private &amp;amp; Provider Plugins&lt;/li&gt;
&lt;li&gt;Load Balancer Healthchecks&lt;/li&gt;
&lt;li&gt;Kubernetes 1.22 support&lt;/li&gt;
&lt;li&gt;TCP Middleware&lt;/li&gt;
&lt;li&gt;Consul Connect integration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The latter is one of the most interesting newcomers. Service Mesh is always an important challenge when dealing with quite large distributed architectures.&lt;/p&gt;

&lt;p&gt;Among Traefik Labs products there is already &lt;a href="https://traefik.io/traefik-mesh/" rel="noopener noreferrer"&gt;Traefik Mesh&lt;/a&gt;, a lightweight Service Mesh, not invasive built on top of Traefik Proxy. These peculiarities are achieved by leaving the "Sidecar Pattern" in favour of a "DaemonSet Pattern", so a per-node instead of a per-pod design pattern.&lt;br&gt;
Using a &lt;code&gt;DaemonSet&lt;/code&gt; in this context brings along a shortcoming (that Traefik Labs has never hidden), &lt;code&gt;mTLS&lt;/code&gt; communication among services is not available and it should be implemented by application itself, for example using Traefik Mesh at TCP level only.&lt;/p&gt;

&lt;p&gt;Consul by &lt;code&gt;HashiCorp&lt;/code&gt; is one of the reference solutions when talking about Service Mesh. Indeed it leverages the Sidecar pattern and offers mTLS communication.&lt;/p&gt;
&lt;h2&gt;
  
  
  What really is Service Mesh?
&lt;/h2&gt;

&lt;p&gt;Let's take a step back on service mesh.&lt;br&gt;
Service Mesh is intended as a network layer in the cluster that allows fast and reliable communication among services and applications within the cluster itself.&lt;/p&gt;

&lt;p&gt;Service mesh is based on this concepts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Discoverability&lt;/code&gt; - services need to discover each other, in order to be able to talk together.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Configurability&lt;/code&gt; - services need to be configured. There should be an abstraction layer for network (no need to know by applications)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Segmentation&lt;/code&gt; - services need fine-grained access control between them, to ensure that only specific sets of services should be able to communicate with each other. In a few words traffic control.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That said, how do Consul components deal with all these concepts?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.consul.io/api-docs/catalog" rel="noopener noreferrer"&gt;Consul Catalog&lt;/a&gt; registers all of your services into a directory. They will be discoverable throughout all the network [&lt;em&gt;Service Discoverability&lt;/em&gt;]&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.consul.io/docs/connect" rel="noopener noreferrer"&gt;Consul Connect&lt;/a&gt; is a proxy layer that routes all service-to-service traffic through an encrypted and authenticated (Mutual TLS) tunnel. [&lt;em&gt;Abstraction layer&lt;/em&gt;]&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.consul.io/docs/connect/intentions" rel="noopener noreferrer"&gt;Consul Intentions&lt;/a&gt; acts as a service-to-service firewall, and authorization system. Full segmentation can be easily achieved by creating a &lt;code&gt;Deny all&lt;/code&gt; rule, and then adding each explicit service-to-service connection rule needed. [&lt;em&gt;Traffic Control&lt;/em&gt;]&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As stated before Consul leverages the &lt;code&gt;Sidecar Pattern&lt;/code&gt;, injecting a sidecar container for each service Pod in the mesh.&lt;br&gt;
This sidecar container is called &lt;strong&gt;Envoy Proxy&lt;/strong&gt; and:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it routes traffic among services pods via other sidecar containers&lt;/li&gt;
&lt;li&gt;it enforces the firewall rules in the mesh network using &lt;code&gt;Intentions&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The following diagram illustrates how the communication works when to service works:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;target service is discovered via Consul Catalog&lt;/li&gt;
&lt;li&gt;service access is verified and ruled by Consul Intentions&lt;/li&gt;
&lt;li&gt;traffic flows from source service via Envoy Proxy through corresponding Envoy Proxy of target service and finally to the target service itself.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flearn.hashicorp.com%2Fimg%2Fconsul%2Fbasic-proxy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flearn.hashicorp.com%2Fimg%2Fconsul%2Fbasic-proxy.png" alt="Envoy Proxy communication" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Working with Consul Connect in Kubernetes
&lt;/h2&gt;

&lt;p&gt;Let's see how the previous concepts can be applied to Kubernetes. Service registration to Consul Connect can be achieved by adding a simple annotation in Pod Spec:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;...&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="s"&gt;...&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="s"&gt;...&lt;/span&gt;
      &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;consul.hashicorp.com/connect-inject&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The previous annotation will automatically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;inject Envoy Proxy as sidecar container&lt;/li&gt;
&lt;li&gt;register a new service in the Consul Catalog&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is another annotation we should take into account.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;consul.hashicorp.com/service-sync&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will see next why this is a relevant annotation. But at the moment let's remember that this will exclusively register a service into the Consul Catalog, skipping the Envoy Proxy injection.&lt;/p&gt;

&lt;h2&gt;
  
  
  Traefik Proxy 2.5 - Consul Connect integration
&lt;/h2&gt;

&lt;p&gt;After explaining a little bit of Consul let's see how &lt;code&gt;Traefik Proxy&lt;/code&gt; is integrating Consul Connect.&lt;/p&gt;

&lt;p&gt;First of all Traefik Proxy will drop the &lt;code&gt;Sidecar Proxy&lt;/code&gt; (similarly to Traefik Mesh), instead a Traefik native support to Consul Connect will be in charge.&lt;br&gt;
Eventually, since Traefik Proxy is based on the concept of &lt;a href="https://doc.traefik.io/traefik/providers/overview/" rel="noopener noreferrer"&gt;Providers&lt;/a&gt; which enables configuration discovery, the &lt;code&gt;Consul Catalog provider&lt;/code&gt; will be the one in charge.&lt;/p&gt;

&lt;p&gt;Let's now go back for a while at the previous annotations of Consul Connect.&lt;br&gt;
&lt;code&gt;consul.hashicorp.com/service-sync&lt;/code&gt; is exactly what is useful: it does not inject sidecar Envoy Proxy, but it rather registers the service into the Consul Catalog. So in order to allow Traefik Proxy to be seen by Consul without injecting it with a sidecar container, the previous annotation will be required.&lt;/p&gt;
&lt;h2&gt;
  
  
  Hands-On
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Requirements
&lt;/h3&gt;

&lt;p&gt;From here on we will suppose you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a configured and working K8s cluster&lt;/li&gt;
&lt;li&gt;Helm installed and available&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All the commands can be found at the following &lt;a href="https://github.com/sw360cab/traefik-consul-demo" rel="noopener noreferrer"&gt;repository&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Consul Connect Configuration
&lt;/h3&gt;

&lt;p&gt;First of all we will leverage &lt;code&gt;Helm&lt;/code&gt; to install &lt;code&gt;Consul&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm repo add hashicorp https://helm.releases.hashicorp.com
helm repo update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is a list of possible values for the &lt;code&gt;Consul Helm Chart&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;global&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;consul&lt;/span&gt;
  &lt;span class="na"&gt;datacenter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dc1&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hashicorp/consul:1.10&lt;/span&gt;
  &lt;span class="na"&gt;imageEnvoy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;envoyproxy/envoy:v1.19-latest&lt;/span&gt;
  &lt;span class="na"&gt;metrics&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;tls&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="c1"&gt;#   enableAutoEncrypt: true&lt;/span&gt;
  &lt;span class="c1"&gt;#   verify: true&lt;/span&gt;
  &lt;span class="c1"&gt;#   serverAdditionalDNSSANs:&lt;/span&gt;
  &lt;span class="c1"&gt;#     ## Add the K8s domain name to the consul server certificate&lt;/span&gt;
  &lt;span class="c1"&gt;#     - "consul-server.consul-system.svc.cluster.local"&lt;/span&gt;
&lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Scale this according to your needs:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;affinity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
&lt;span class="na"&gt;ui&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;controller&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;  
&lt;span class="na"&gt;connectInject&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# This method will inject the sidecar container into Pods:&lt;/span&gt;
  &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="c1"&gt;# But not by default, only do this for Pods that have the explicit annotation:&lt;/span&gt;
  &lt;span class="c1"&gt;#        consul.hashicorp.com/connect-inject: "true"&lt;/span&gt;
  &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="na"&gt;syncCatalog&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# This method will automatically synchronize Kubernetes services to Consul:&lt;/span&gt;
  &lt;span class="c1"&gt;# (No sidecar is injected by this method):&lt;/span&gt;
  &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="c1"&gt;# But not by default, only for Services that have the explicit annotation:&lt;/span&gt;
  &lt;span class="c1"&gt;#        consul.hashicorp.com/service-sync: "true"&lt;/span&gt;
  &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="c1"&gt;# Synchronize from Kubernetes to Consul:&lt;/span&gt;
  &lt;span class="na"&gt;toConsul&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="c1"&gt;# But not from Consul to K8s:&lt;/span&gt;
  &lt;span class="na"&gt;toK8S&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note&lt;/em&gt;: I explicitly disabled the following section which prevented Consul Helm Chart to be fully bootstrapped:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;enableAutoEncrypt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="s"&gt;verify&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="s"&gt;serverAdditionalDNSSANs&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;## Add the K8s domain name to the consul server certificate&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;consul-server.consul-system.svc.cluster.local"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First create a namespace, then run the Helm chart:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl create namespace consul-system
helm upgrade &lt;span class="nt"&gt;--install&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; consul-values.yaml consul &lt;span class="se"&gt;\&lt;/span&gt;
  hashicorp/consul &lt;span class="nt"&gt;--namespace&lt;/span&gt; consul-system
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can adjust these values as you like BUT remember in order to leverage Traefik Proxy integration, Envoy Proxy should not be enabled by default. So basically the advice here is disabling by default Envoy Proxy Injection and Service Registration in Helm values.&lt;/p&gt;

&lt;h3&gt;
  
  
  Traefik Proxy Configuration
&lt;/h3&gt;

&lt;p&gt;Traefik Proxy can also be configured via Helm, but I'd rather configure it explicitly to exploit its simplicity and straightforwardness.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Important&lt;/code&gt;: Before proceeding remember that Traefik Proxy needs a copy of the Consul certificate authority TLS certificate. Copy the &lt;code&gt;Secret&lt;/code&gt; resource from the consul namespace into the traefik namespace:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;kubectl get secret consul-ca-cert -n consul-system -oyaml | \&lt;/span&gt;
  &lt;span class="s"&gt;sed 's/namespace&lt;/span&gt;&lt;span class="na"&gt;: consul-system$/namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;traefik-system/' | \&lt;/span&gt;
  &lt;span class="s"&gt;kubectl apply -f -&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then remember to mount the secret into the Deployment of Traefik Proxy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;consul-ca-cert&lt;/span&gt;
    &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/certs/consul-ca/"&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;secret&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;Traefik Proxy CLI&lt;/code&gt; arguments are those which enable Consul Catalog provider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="c1"&gt;## Consul config: &lt;/span&gt;
  &lt;span class="c1"&gt;# Enable Traefik to use Consul Connect:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--providers.consulcatalog.connectAware=true"&lt;/span&gt; 
  &lt;span class="c1"&gt;# Traefik routes should only be created for services with explicit `traefik.enable=true` service-tags:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--providers.consulcatalog.exposedByDefault=false"&lt;/span&gt;
  &lt;span class="c1"&gt;# For routes that are exposed (`traefik.enable=true`) use Consul Connect by default:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--providers.consulcatalog.connectByDefault=true"&lt;/span&gt;
  &lt;span class="c1"&gt;# Rename the service inside Consul: `traefik-system-ingress`&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--providers.consulcatalog.servicename=traefik-system-ingress"&lt;/span&gt;
  &lt;span class="c1"&gt;# Connect Traefik to the Consul service:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--providers.consulcatalog.endpoint.address=consul-server.&amp;lt;consul_namespace&amp;gt;.svc.cluster.local:8501"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--providers.consulcatalog.endpoint.scheme=https"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--providers.consulcatalog.endpoint.tls.ca=/certs/consul-ca/tls.crt"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cli arguments above include the reference to the Consul CA certificate created via a secret and mounted on a volume.&lt;br&gt;
Moreover as stated above, the service will be &lt;em&gt;discoverable&lt;/em&gt; by Consul leveraging annotations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;only &lt;code&gt;service-sync&lt;/code&gt; is enabled (not the Envoy Proxy injection)&lt;/li&gt;
&lt;li&gt;there is a correspondence between the &lt;code&gt;service name&lt;/code&gt; in the annotations and in the Traefik CLI arguments
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;consul.hashicorp.com/service-sync&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
&lt;span class="na"&gt;consul.hashicorp.com/service-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik-system-ingress"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Here the full Traefik Proxy configuration (Deployment+Service):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ServiceAccount&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;traefik-ingress-controller&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;traefik-consul&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;traefik&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;traefik-consul&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;traefik&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;traefik&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;traefik&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;serviceAccountName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;traefik-ingress-controller&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;traefik&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;traefik:v2.5&lt;/span&gt;
          &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--api.insecure&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--accesslog&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--entrypoints.web.address=:80&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--entrypoints.websecure.address=:443&lt;/span&gt;
            &lt;span class="c1"&gt;# Consul config: &lt;/span&gt;
            &lt;span class="c1"&gt;# Enable Traefik to use Consul Connect:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--providers.consulcatalog.connectAware=true"&lt;/span&gt; 
            &lt;span class="c1"&gt;# Traefik routes should only be created for services with explicit `traefik.enable=true` service-tags:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--providers.consulcatalog.exposedByDefault=false"&lt;/span&gt;
            &lt;span class="c1"&gt;# For routes that are exposed (`traefik.enable=true`) use Consul Connect by default:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--providers.consulcatalog.connectByDefault=true"&lt;/span&gt;
            &lt;span class="c1"&gt;# Rename the service inside Consul: `traefik-system-ingress`&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--providers.consulcatalog.servicename=traefik-system-ingress"&lt;/span&gt;
            &lt;span class="c1"&gt;# Connect Traefik to the Consul service:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--providers.consulcatalog.endpoint.address=consul-server.consul-system.svc.cluster.local:8501"&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--providers.consulcatalog.endpoint.scheme=https"&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--providers.consulcatalog.endpoint.tls.ca=/certs/consul-ca/tls.crt"&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--providers.consulcatalog.endpoint.tls.insecureskipverify=true"&lt;/span&gt;
          &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web&lt;/span&gt;
              &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;websecure&lt;/span&gt;
              &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;443&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;admin&lt;/span&gt;
              &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
          &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2"&lt;/span&gt;
              &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;500Mi"&lt;/span&gt;
          &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;consul-ca-cert&lt;/span&gt;
              &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/certs/consul-ca/"&lt;/span&gt;
              &lt;span class="na"&gt;readOnly&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;consul-ca-cert&lt;/span&gt;
        &lt;span class="na"&gt;secret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;secretName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;consul-ca-cert&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;traefik&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;traefik-consul&lt;/span&gt;
  &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
    &lt;span class="c1"&gt;# Register the service in Consul as `traefik-system-ingress`:&lt;/span&gt;
    &lt;span class="na"&gt;consul.hashicorp.com/service-sync&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
    &lt;span class="na"&gt;consul.hashicorp.com/service-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik-system-ingress"&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;traefik&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web&lt;/span&gt;
    &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
    &lt;span class="na"&gt;nodePort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30080&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
    &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;admin&lt;/span&gt;
    &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
    &lt;span class="na"&gt;nodePort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30808&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
    &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;webtls&lt;/span&gt;
    &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
    &lt;span class="na"&gt;nodePort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30443&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;443&lt;/span&gt;
    &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;443&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If everything works, accessing the Consul UI you should find a service named "traefik-system-ingress" registered via Kubernetes.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Tip&lt;/code&gt;: in a development environment &lt;code&gt;Port Forwarding&lt;/code&gt; is an easy and straightforward way to access Consul UI from your own browser&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl port-forward &lt;span class="nt"&gt;--address&lt;/span&gt; 0.0.0.0 &lt;span class="nt"&gt;--namespace&lt;/span&gt; consul-system service/consul-ui 18500:443
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you can access Consul UI at &lt;code&gt;https://&amp;lt;your host IP&amp;gt;:18500/&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Configure a service route by adding Consul tags to Services
&lt;/h3&gt;

&lt;p&gt;Let's now add a service to the Consul service Mesh.&lt;br&gt;
From the Consul side the service only needs the Envoy sidecar proxy (remember the annotation: &lt;code&gt;consul.hashicorp.com/connect-inject: "true"&lt;/code&gt;).&lt;br&gt;
However the same service may be exposed also on the Traefik Proxy side, and here is where the magic happens, and there are two key components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the &lt;code&gt;Consul Catalog provider&lt;/code&gt; from the Traefik domain&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;service tags&lt;/code&gt; annotations from the Consul domain&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When using the Consul Catalog provider, Traefik Proxy routes are added by creating service tags in Consul itself. This can be done via the Consul HTTP API, or from the Kubernetes API, by adding an annotation in the Deployment Pod spec: &lt;code&gt;consul.hashicorp.com/service-tags&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Following the relevant part:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;...&lt;/span&gt;
    &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="s"&gt;….&lt;/span&gt;
    &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="s"&gt;...&lt;/span&gt;
        &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;consul.hashicorp.com/connect-inject&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
          &lt;span class="na"&gt;consul.hashicorp.com/service-tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;            &lt;span class="s"&gt;traefik.enable=true,&lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;            &lt;span class="s"&gt;traefik.http.routers.whoami.entrypoints=web,&lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;            &lt;span class="s"&gt;traefik.http.routers.whoami.rule=Host(`ks3.minimalgap.com`)"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The value for the &lt;code&gt;service-tags&lt;/code&gt; annotation must be all in one line, tags separated with commas, and no spaces. In order to make it more readable, the line is wrapped onto multiple lines with the &lt;code&gt;\&lt;/code&gt; character after the commas.&lt;br&gt;
This is different by employing a pure Kubernetes Provider Configuration in Traefik Proxy, where you would expect these values in the definition of an &lt;code&gt;Ingress&lt;/code&gt; component.&lt;/p&gt;

&lt;p&gt;After deploying a service annotated as above, if you now access the Traefik Dashboard you will notice that the &lt;code&gt;Whoami&lt;/code&gt; service is now marked with &lt;code&gt;Consul Catalog&lt;/code&gt; provider.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Note&lt;/code&gt;: Remember to set up a valid and correct FQDN in the &lt;code&gt;service-tags&lt;/code&gt; annotation for the &lt;code&gt;Host Rule&lt;/code&gt; in the router section.&lt;/p&gt;
&lt;h2&gt;
  
  
  Note on TLS
&lt;/h2&gt;

&lt;p&gt;You may note that the service above is exposed on a simple HTTP endpoint, skipping TLS support.&lt;br&gt;
I struggled a little bit with configuring TLS. As far as I know a &lt;code&gt;Let's Encrypt resolver&lt;/code&gt; should be employed.&lt;/p&gt;

&lt;p&gt;This requires a further configuration for the Traefik Proxy CLI, e.g.:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;## TLS CHALLENGE&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--certificatesresolvers.autoresolver.acme.tlschallenge&lt;/span&gt;
&lt;span class="c1"&gt;## LetsEncrypt definitions&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--certificatesresolvers.autoresolver.acme.email=foo@you.com&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--certificatesresolvers.autoresolver.acme.storage=acme.json&lt;/span&gt;
&lt;span class="c1"&gt;## Please note that this is the staging Let's Encrypt server.&lt;/span&gt;
&lt;span class="c1"&gt;## Once you get things working, you should remove that whole line altogether.&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--certificatesresolvers.autoresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and the corresponding reference in the service discovered via Consul, changing the service-tags annotation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;traefik.enable=true,\&lt;/span&gt;
  &lt;span class="s"&gt;traefik.http.routers.whoami.entrypoints=web,\&lt;/span&gt;
  &lt;span class="s"&gt;traefik.http.routers.whoami.rule=Host(`ks3.minimalgap.com`),\&lt;/span&gt;
  &lt;span class="s"&gt;traefik.http.routers.whoami.tls=true,\&lt;/span&gt;
  &lt;span class="s"&gt;traefik.http.routers.whoami.tls.certresolver=autoresolver"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since this topic it's outside of the scope of this post, for sake of simplicity I left apart the TLS configuration.&lt;br&gt;
I advise you to carefully read the Traefik Proxy docs to attempt a fully TLS configuration; there is a detailed User Guide section for &lt;a href="https://doc.traefik.io/traefik/user-guides/crd-acme/" rel="noopener noreferrer"&gt;Kubernetes &amp;amp; Let's Encrypt&lt;/a&gt;.&lt;br&gt;
Moreover the whole process would be much simpler if you own a domain that is compliant with the &lt;a href="https://doc.traefik.io/traefik/https/acme/#dnschallenge" rel="noopener noreferrer"&gt;dnsChallenge&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Playing with Consul Intentions
&lt;/h3&gt;

&lt;p&gt;It's now time for fun!&lt;br&gt;
So far we have configured:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Consul Connect (via Helm)&lt;/li&gt;
&lt;li&gt;Traefik Proxy (with Consul Catalog provider)&lt;/li&gt;
&lt;li&gt;a service (&lt;em&gt;whoami&lt;/em&gt;) that is &lt;em&gt;visible&lt;/em&gt; by both Consul Connect and Traefik Proxy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now we can access the service using &lt;code&gt;cUrl&lt;/code&gt;. This can be tricky depending from your configuration, you may use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Port Forwarding&lt;/code&gt; the whole Traefik service (Gotcha &lt;code&gt;sudoing&lt;/code&gt;, check &lt;a href="https://doc.traefik.io/traefik/user-guides/crd-acme/#port-forwarding" rel="noopener noreferrer"&gt;here&lt;/a&gt;)
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl  port-forward &lt;span class="nt"&gt;--address&lt;/span&gt; 0.0.0.0 service/traefik 80:80 8080:8080 &lt;span class="nt"&gt;-n&lt;/span&gt; traefik-consul
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Attaching a &lt;code&gt;shell&lt;/code&gt; to a Pod and executing &lt;em&gt;curl&lt;/em&gt; from there (if cUrl is available within the Pod itself)
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-ti&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; consul-system consul-server-0 &lt;span class="nt"&gt;--&lt;/span&gt; curl http://ks3.minimalgap.com:80
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Creating a &lt;code&gt;K8s IngressRoute&lt;/code&gt; in Traefik Proxy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's suppose one of the above finally works. The response would be something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://ks3.minimalgap.com:80 &lt;span class="nt"&gt;-vv&lt;/span&gt;
&lt;span class="k"&gt;*&lt;/span&gt;   Trying 34.240.156.240:80...
&lt;span class="k"&gt;*&lt;/span&gt; Connected to ks3.minimalgap.com &lt;span class="o"&gt;(&lt;/span&gt;34.240.156.240&lt;span class="o"&gt;)&lt;/span&gt; port 80 &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="c"&gt;#0)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; GET / HTTP/1.1
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Host: ks3.minimalgap.com
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; User-Agent: curl/7.79.1
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Accept: &lt;span class="k"&gt;*&lt;/span&gt;/&lt;span class="k"&gt;*&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;*&lt;/span&gt; Mark bundle as not supporting multiuse
&amp;lt; HTTP/1.1 200 OK
IP: 127.0.0.1
IP: ::1
IP: 10.42.0.140
IP: fe80::d09e:6dff:feee:cb78
RemoteAddr: 127.0.0.1:43100
GET / HTTP/1.1
Host: ks3.minimalgap.com
User-Agent: curl/7.79.1
Accept: &lt;span class="k"&gt;*&lt;/span&gt;/&lt;span class="k"&gt;*&lt;/span&gt;
Accept-Encoding: &lt;span class="nb"&gt;gzip
&lt;/span&gt;X-Forwarded-For: 10.42.0.139
X-Forwarded-Host: ks3.minimalgap.com
X-Forwarded-Port: 80
X-Forwarded-Proto: http
X-Forwarded-Server: traefik-f9bbb4fb8-fzx6v
X-Real-Ip: 10.42.0.139
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point we can state we achieved a working configuration. It's time to create &lt;code&gt;Intentions&lt;/code&gt;!&lt;br&gt;
Consul Intentions are more or less firewall rules for services, they have a Source Service, a Target Service, a Description and a Deny/Allow option.&lt;br&gt;
These rules can easily be visually created from the Consul Dashboard. Suppose that we want to create a Deny All rule:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;* (All Services) for the Source Service&lt;/li&gt;
&lt;li&gt;* (All Services) for the Destination Service&lt;/li&gt;
&lt;li&gt;Should this source connect to the destination? choose Deny&lt;/li&gt;
&lt;li&gt;click Save to create the Intention.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now test the &lt;em&gt;whoami&lt;/em&gt; service again, using your real service domain name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://ks3.minimalgap.com:80 &lt;span class="nt"&gt;-vv&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see a response of &lt;code&gt;502 Bad Gateway&lt;/code&gt;.&lt;br&gt;
&lt;code&gt;Note&lt;/code&gt;: the response may be still &lt;em&gt;200 OK&lt;/em&gt; for few minutes. This is because the Intentions may take some time to update to the envoy sidecar. I promise after a while the Bad Gateway response should come up!&lt;/p&gt;

&lt;p&gt;Alternatively Consul Intentions can be configured via Kubernetes API. This is achieved via K8s CRDs. Let's use this method to configure an &lt;code&gt;Allow&lt;/code&gt; Rule from the Traefik Proxy (&lt;code&gt;traefik-system-ingress&lt;/code&gt;) to the destination service (&lt;em&gt;whoami&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;First define a Service Definition CRD:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;consul.hashicorp.com/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ServiceDefaults&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;whoami&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;consul-demo&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;http'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Service Intentions can be defined via YAML in a similar way respect to Consul Dashboard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;consul.hashicorp.com/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ServiceIntentions&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;whoami&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;consul-demo&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Name of the destination service affected by this ServiceIntentions entry.&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;whoami&lt;/span&gt;
  &lt;span class="na"&gt;sources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# The set of traffic sources affected by this ServiceIntentions entry.&lt;/span&gt;
    &lt;span class="c1"&gt;# When the source is the Traefik Proxy:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;traefik-system-ingress&lt;/span&gt;
      &lt;span class="c1"&gt;# The set of permissions to apply for Traefik Proxy to access whoami:&lt;/span&gt;
      &lt;span class="c1"&gt;# The first permission to match in the list is terminal, and stops further evaluation.&lt;/span&gt;
      &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;allow&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Accessing the endpoint should stop giving (quite immediately) a &lt;code&gt;Bad Gateway&lt;/code&gt; response in favour of a successful response again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://traefik.io/" rel="noopener noreferrer"&gt;Traefik Labs&lt;/a&gt; keeps on doing giant leaps and integrating Consul Connect is another step beyond for &lt;code&gt;Traefik Proxy&lt;/code&gt;. This indicates the path of this product is humble but reliable and flexible, with an open-minded philosophy behind that is never scared of comparing and collaborating with other important competitors and actors in the CNCF big landscape picture.&lt;/p&gt;

&lt;p&gt;Probably &lt;code&gt;Consul Connect&lt;/code&gt; is a product more suitable in advanced use cases where big clusters are involved. But Consul Connect integration is a kind of Enterprise level feature that demonstrates how Traefik Proxy is powerful: it works the same way, from the homemade pet project to a huge diverse enterprise cluster.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>cloud</category>
      <category>consul</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Minimal Gap is online</title>
      <dc:creator>Sergio Matone</dc:creator>
      <pubDate>Tue, 28 Sep 2021 09:09:53 +0000</pubDate>
      <link>https://dev.to/sw360cab/minimal-gap-is-online-hlj</link>
      <guid>https://dev.to/sw360cab/minimal-gap-is-online-hlj</guid>
      <description>&lt;p&gt;I would like to share my brand new website for my freelance activity: &lt;a href="https://minimalgap.com" rel="noopener noreferrer"&gt;MinimalGap&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As the brand name says, I decided to go for a &lt;em&gt;minimal&lt;/em&gt; landing page to give a taste of who I am and what I can do in the huge world of IT tech.&lt;/p&gt;

&lt;p&gt;Feedback is welcome so feel free to comment on this.&lt;/p&gt;

</description>
      <category>cloudskills</category>
      <category>freelance</category>
      <category>personalwebsite</category>
    </item>
    <item>
      <title>What a trip the "Traefik Autumn Edition": true memories from a Traefik Ambassador</title>
      <dc:creator>Sergio Matone</dc:creator>
      <pubDate>Wed, 23 Dec 2020 13:39:33 +0000</pubDate>
      <link>https://dev.to/sw360cab/what-a-trip-the-traefik-autumn-edition-true-memories-from-a-traefik-ambassador-1j0p</link>
      <guid>https://dev.to/sw360cab/what-a-trip-the-traefik-autumn-edition-true-memories-from-a-traefik-ambassador-1j0p</guid>
      <description>&lt;p&gt;It was April the 24th 2020, we were in the middle of the lockdown because of the global pandemic, among the tons of emails (most of them Adv) received everyday, there was one coming from Patricia Dugan from Traefik Labs, which says I was entitled as Traefik Ambassador.&lt;br&gt;
What? Let me double check that this is not a spam email which will let me win a couple of iPhones after giving all my personal data and possibly my credit card number. No it's not, she really wanted to communicate that to me.&lt;/p&gt;

&lt;p&gt;Wow, I am so proud, I am so happy! Yeeeaaah... But, wait a second, from great powers come great responsibilities. Which are my duties, what should I do? I have been a Traefik Ambassador since half an hour and I still don't know what should I do, that is a shame. Soon they will realize that I do not deserve to be a Traefik Ambassador, what a shame! I am so sad 🤕.&lt;br&gt;
I was already seeing myself speaking at a conference, presented as a Traefik Ambassador. This would never happen, moreover conferences are not feasible too, because of this damned Covid-19. I remember when we used to meet at the CNCF Meetup here in Torino (🇮🇹), with Gianluca, Barbara, Giuseppe and all the others, and after listening to great talks we just used to have friendly chat in front of 🍺 and 🍕. Oh, beloved old days, will these ever happen again? When will we be able to organize a meeting in person again? Oh, but wait a second, let me remove the "in person" part… Organizing meeting 🤔, Traefik Ambassador... mmm, maybe I'm still on track. If I ask both Gianluca and Patricia to join forces to organize something together, I can still deserve my Traefik Ambassador entitlement.&lt;/p&gt;

&lt;p&gt;Whether the previous lines happened in my mind in a couple of hours after receiving the news of me becoming a Traefik Ambassador, I can assure that what happened next took more or less a couple days.&lt;br&gt;
I went through proposing that to Gianluca and Patricia, surprisingly they were both very positive about the proposal, so that with &amp;lt; 20 emails we were done in setting up not only a single event but a complete season of events. Thanks to Gianluca we found immediately a nicename: "Traefik Autumn Edition". The rest went so smoothly that I can barely remember anything stopping our flow 💪🏽.&lt;/p&gt;

&lt;p&gt;The drawback was that eventually I was in charge for the very first event! 😱&lt;/p&gt;
&lt;h2&gt;
  
  
  Episodes 01: Introducing Traefik Proxy 2.3. A Journey into Traefik Plugins via Gitops [29/10/2020]
&lt;/h2&gt;

&lt;p&gt;And here we go: first event.&lt;/p&gt;


&lt;blockquote&gt;
&lt;p&gt;I'm so glad we bring back the &lt;a href="https://twitter.com/hashtag/CNCF?src=hash&amp;amp;ref_src=twsrc%5Etfw" rel="noopener noreferrer"&gt;#CNCF&lt;/a&gt; Turin meetup with an Awesome series about &lt;a href="https://twitter.com/hashtag/Traefik?src=hash&amp;amp;ref_src=twsrc%5Etfw" rel="noopener noreferrer"&gt;#Traefik&lt;/a&gt;. I am kicking off my adventure as &lt;a href="https://twitter.com/hashtag/traefikambassador?src=hash&amp;amp;ref_src=twsrc%5Etfw" rel="noopener noreferrer"&gt;#traefikambassador&lt;/a&gt;. Thx to &lt;a href="https://twitter.com/traefik?ref_src=twsrc%5Etfw" rel="noopener noreferrer"&gt;@traefik&lt;/a&gt; &lt;a href="https://twitter.com/cncf?ref_src=twsrc%5Etfw" rel="noopener noreferrer"&gt;@CNCF&lt;/a&gt; &lt;a href="https://twitter.com/patricia_dugan?ref_src=twsrc%5Etfw" rel="noopener noreferrer"&gt;@patricia_dugan&lt;/a&gt; and &lt;a href="https://twitter.com/GianArb?ref_src=twsrc%5Etfw" rel="noopener noreferrer"&gt;@GianArb&lt;/a&gt; for making it possible and letting the &lt;a href="https://twitter.com/hashtag/Traefik?src=hash&amp;amp;ref_src=twsrc%5Etfw" rel="noopener noreferrer"&gt;#Traefik&lt;/a&gt; community growing in 🇮🇹. &lt;br&gt;Stay Tuned!📣 &lt;a href="https://t.co/PNlhsIk0Ye" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;a href="https://t.co/PNlhsIk0Ye" rel="noopener noreferrer"&gt;https://t.co/PNlhsIk0Ye&lt;/a&gt;&lt;/p&gt;— Sergio Matone (&lt;a class="mentioned-user" href="https://dev.to/sw360cab"&gt;@sw360cab&lt;/a&gt;) &lt;a href="https://twitter.com/sw360cab/status/1313215872656379905?ref_src=twsrc%5Etfw" rel="noopener noreferrer"&gt;October 5, 2020&lt;/a&gt;
&lt;/blockquote&gt;  

&lt;p&gt;Although very excited, I succeeded in introducing the Traefik Labs Stack. I then explored Traefik Proxy and what is new in version 2.3. Finally I went through an overview of Traefik Plugins. The whole event was topped with extended demos and live coding. I showed basic usage of Traefik Proxy in Kubernetes environment, going through the definition of a Traefik Plugins (I made my own for the event). &lt;/p&gt;

&lt;p&gt;Demo gods assisted me almost till the end when I tried reflecting edits in code leveraging GitOperations, when the demo using ArgoCD did not work... But I can prove it works with a video!&lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/VgHrK5xIPT8"&gt;
&lt;/iframe&gt;
 &lt;/p&gt;

&lt;p&gt;and here are the slides:&lt;br&gt;
&lt;a href="https://speakerdeck.com/sw360cab/traefik-autumn-edition-2020-s01e01" rel="noopener noreferrer"&gt;https://speakerdeck.com/sw360cab/traefik-autumn-edition-2020-s01e01&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;and the Traefik plugin code:&lt;br&gt;
&lt;a href="https://github.com/sw360cab/cncftaeplugin" rel="noopener noreferrer"&gt;https://github.com/sw360cab/cncftaeplugin&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Full video of the Event is here:&lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/AISnDSgcHoA"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Episodes 02:  Kubernetes Ingress Today and Tomorrow [19/11/2020]
&lt;/h2&gt;

&lt;p&gt;Second event was immediately on his way:&lt;/p&gt;


&lt;blockquote&gt;
&lt;p&gt;Couple weeks ago Episode 01 of &lt;a href="https://twitter.com/hashtag/Traefik?src=hash&amp;amp;ref_src=twsrc%5Etfw" rel="noopener noreferrer"&gt;#Traefik&lt;/a&gt; Autumn Edition 🍂&lt;br&gt;was on the edge! Now it's time to look forward for Episodes 02,🗓 19th Nov fo: &lt;a href="https://twitter.com/hashtag/Kubernetes?src=hash&amp;amp;ref_src=twsrc%5Etfw" rel="noopener noreferrer"&gt;#Kubernetes&lt;/a&gt; Ingress Today and Tomorrow. Presented by Kevin Crawley (&lt;a href="https://twitter.com/notsureifkevin?ref_src=twsrc%5Etfw" rel="noopener noreferrer"&gt;@notsureifkevin&lt;/a&gt;) at &lt;a href="https://twitter.com/hashtag/CNCF?src=hash&amp;amp;ref_src=twsrc%5Etfw" rel="noopener noreferrer"&gt;#CNCF&lt;/a&gt; Turin. Join us: &lt;br&gt;👉 &lt;a href="https://t.co/zT2zz8GqFH" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;a href="https://t.co/zT2zz8GqFH" rel="noopener noreferrer"&gt;https://t.co/zT2zz8GqFH&lt;/a&gt; &lt;a href="https://t.co/3SQGpDAyC2" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;a href="https://t.co/3SQGpDAyC2" rel="noopener noreferrer"&gt;https://t.co/3SQGpDAyC2&lt;/a&gt;&lt;/p&gt;— Sergio Matone (&lt;a class="mentioned-user" href="https://dev.to/sw360cab"&gt;@sw360cab&lt;/a&gt;) &lt;a href="https://twitter.com/sw360cab/status/1327290935017926658?ref_src=twsrc%5Etfw" rel="noopener noreferrer"&gt;November 13, 2020&lt;/a&gt;
&lt;/blockquote&gt;  

&lt;p&gt;Kevin Crawley aka &lt;a href="https://twitter.com/notsureifkevin" rel="noopener noreferrer"&gt;@notsureifkevin&lt;/a&gt; (yes I am sure it was him!), from Traefik Labs,  drove us through the new possibilities given by release 1.18 of Kubernetes. He highlighted many notable changes including adding the new IngressClass resource and modifications made to the Ingress resource. It introduces a new field that specifies how Ingress paths should match and support wildcard domains. Kevin had a great demo using Traefik Proxy, MetalLB, and Kind.&lt;br&gt;
We then had a great excursus of the new Service API specification in Kubernetes. This new API aims to bring vendor neutrality to the Ingress networking landscape. Kevin demonstrated how these recent changes operate using Traefik Proxy.&lt;/p&gt;

&lt;p&gt;We finally had a great networking time where we were able to discuss how hard it is to achieve working configurations in Kubernetes when dealing with real world scenarios, even some shortcomings in Traefik Proxy that they are willing to improve soon.&lt;/p&gt;

&lt;p&gt;Here are the slides: &lt;br&gt;
&lt;a href="https://docs.google.com/presentation/d/1-YpI9StVmaRgIkTq-0Hy0CdfX8OlOcv5xFJoeyevVRU/edit#slide=id.g9e184c83e8_0_5" rel="noopener noreferrer"&gt;https://docs.google.com/presentation/d/1-YpI9StVmaRgIkTq-0Hy0CdfX8OlOcv5xFJoeyevVRU/edit#slide=id.g9e184c83e8_0_5&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Full video of the Event is here:&lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/2fJrEbSq4c8"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Episodes 03: How to monitor Traefik workloads with Prometheus &amp;amp; Grafana [17/12/2020]
&lt;/h2&gt;

&lt;p&gt;In an heartbeat we were at the Final Episode:&lt;/p&gt;


&lt;blockquote&gt;
&lt;p&gt;I still need to recover from last Episode at &lt;a href="https://twitter.com/hashtag/CNCF?src=hash&amp;amp;ref_src=twsrc%5Etfw" rel="noopener noreferrer"&gt;#CNCF&lt;/a&gt; Turin that was stunning! Now I need to pull myself together for the Great season Finale of &lt;a href="https://twitter.com/hashtag/Traefik?src=hash&amp;amp;ref_src=twsrc%5Etfw" rel="noopener noreferrer"&gt;#Traefik&lt;/a&gt; Autumn Edition, which will be held by &lt;a href="https://twitter.com/idomyowntricks?ref_src=twsrc%5Etfw" rel="noopener noreferrer"&gt;@idomyowntricks&lt;/a&gt;. Sign up ASAP 👇&lt;a href="https://twitter.com/hashtag/traefikambassador?src=hash&amp;amp;ref_src=twsrc%5Etfw" rel="noopener noreferrer"&gt;#traefikambassador&lt;/a&gt; &lt;a href="https://t.co/XmfH8hRI3K" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;a href="https://t.co/XmfH8hRI3K" rel="noopener noreferrer"&gt;https://t.co/XmfH8hRI3K&lt;/a&gt;&lt;/p&gt;— Sergio Matone (&lt;a class="mentioned-user" href="https://dev.to/sw360cab"&gt;@sw360cab&lt;/a&gt;) &lt;a href="https://twitter.com/sw360cab/status/1331183641997750273?ref_src=twsrc%5Etfw" rel="noopener noreferrer"&gt;November 24, 2020&lt;/a&gt;
&lt;/blockquote&gt;  

&lt;p&gt;Brian Christner (&lt;a href="https://twitter.com/idomyowntricks" rel="noopener noreferrer"&gt;@idomyowntricks&lt;/a&gt;) from theByte showed the usage of Prometheus and Grafana leveraging native support in Traefik Proxy. He spent a lot of time showing code and GUIs instead of sticking with tons of slides. He answered a lot of doubts, gave advices and took us real-world scenarios &lt;/p&gt;

&lt;p&gt;Besides Docker Captain, Traefik Ambassador and Cloud expert, Brian is hosting online courses. Luckily he gave me a preview of the complete Traefik Proxy course, so I take the chance to suggest everybody taking it. &lt;a href="https://www.thebyte.io/traefik-training" rel="noopener noreferrer"&gt;Check this out&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Slides of the Event:&lt;br&gt;
&lt;a href="https://56k-share.s3.eu-central-1.amazonaws.com/Traefik_Observability.pdf" rel="noopener noreferrer"&gt;https://56k-share.s3.eu-central-1.amazonaws.com/Traefik_Observability.pdf&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Full video of the Event is here:&lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/kNSm0JyFfyU"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Last Words
&lt;/h2&gt;

&lt;p&gt;It has been a real adventure to organize, present and join the first Traefik Autumn Edition. It would not have been possible without the help of Gianluca Arbezzano (&lt;a href="https://twitter.com/GianArb" rel="noopener noreferrer"&gt;@gianarb&lt;/a&gt;) who hosted the whole Season at the &lt;a href="https://www.meetup.com/CNCF-Italy/" rel="noopener noreferrer"&gt;Turin CNCF Meetup&lt;/a&gt; and Patricia Dugan (&lt;a href="https://twitter.com/patricia_dugan" rel="noopener noreferrer"&gt;@patricia&lt;/a&gt;) who is Head of Community at Traefik Labs and who never stopped encouraging and supporting me.&lt;br&gt;
Especially in the period we are living, we started loving online events, streaming, webinars and so on. But actually we have enough of that and I thank all the people who joined and listened for us patiently.&lt;/p&gt;

&lt;p&gt;A final thanks goes to &lt;a href="https://www.cncf.io/" rel="noopener noreferrer"&gt;CNCF&lt;/a&gt; that allows the existence of its Meetup in my city, Torino (🇮🇹), &lt;a href="https://www.equinix.com/" rel="noopener noreferrer"&gt;Equinix Metal&lt;/a&gt; that gave us bonus VPS for hosting our demos, and &lt;a href="https://traefik.io/" rel="noopener noreferrer"&gt;Traefik Labs&lt;/a&gt; for supporting us and creating a great community around Traefik!&lt;/p&gt;

&lt;p&gt;Stay safe, stay strong.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>traefik</category>
      <category>cncf</category>
      <category>go</category>
    </item>
    <item>
      <title>Mongoose Debug messages with a custom logging library or style</title>
      <dc:creator>Sergio Matone</dc:creator>
      <pubDate>Thu, 05 Nov 2020 17:39:09 +0000</pubDate>
      <link>https://dev.to/sw360cab/mongoose-debug-messages-with-a-custom-logging-library-or-style-1hk4</link>
      <guid>https://dev.to/sw360cab/mongoose-debug-messages-with-a-custom-logging-library-or-style-1hk4</guid>
      <description>&lt;h1&gt;
  
  
  Mongoose Custom Logger
&lt;/h1&gt;

&lt;p&gt;When Using Mongoose as MongoDb object modeling for NodeJs, one interesting feature is the possibility to log to standard output what Mongoose is actually doing while interfacing with MongoDB. This can be easily achieved by configuring Mongoose library itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;mongoose&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;debug&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which will print any action that Mongoose is performing (e.g. insert, update, remove, find) in a colored and very readable fashion.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffa6tyj64h3ab702fkk3w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffa6tyj64h3ab702fkk3w.png" alt="Log Sample" width="800" height="21"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is basically an inner call of the library to &lt;code&gt;console.log&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;However when employing a custom logger library, or a custom logging style, the Mongoose log style becomes soon non-compliant.&lt;br&gt;
Imagine adding a logging library to your project or a logger that logs to file instead of stdout, or even a custom log format for logging to standard output.&lt;br&gt;
In all the former cases Mongoose logs would be lost or not conformant.&lt;/p&gt;

&lt;p&gt;Luckily Mongoose is providing an additional parameter to the previous method. From the official &lt;a href="https://mongoosejs.com/docs/api/mongoose.html#mongoose_Mongoose-set" rel="noopener noreferrer"&gt;doc&lt;/a&gt;, the parameter can be either a writable stream or a callback for logging a message using a custom format.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;mongoose&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;debug&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;collectionName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;methodName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;methodArgs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{});&lt;/span&gt; &lt;span class="c1"&gt;// use custom function to log collection methods + arguments&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;If a writable stream is passed, it will log to that stream, without colorization. If a callback function is passed, it will receive the collection name, the method name, then all arguments passed to the method. For example, if you wanted to replicate the default logging, you could output from the callback Mongoose: ${collectionName}.${methodName}(${methodArgs.join(', ')}).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As stated above the custom callback can be something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;collectionName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;methodName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;methodArgs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;customLogger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;collectionName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;methodName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;(&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;methodArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;, &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;)`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;where &lt;code&gt;customLogger&lt;/code&gt; is a custom logging library or imported dependency. The result is something like that:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpqlbf8lxc83m82ko2ehu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpqlbf8lxc83m82ko2ehu.png" alt="Log No Color" width="800" height="25"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While this is a step forward, this will just print a plain string, throwing away the nice colorization of the original Mongoose method, which is not cool!&lt;br&gt;
Whereas for the &lt;code&gt;Mongoose&lt;/code&gt; string, it is just a matter of pure console coloring optimization, in the form of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;customLogger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`\x1B[0;36mMongoose:\x1B[0m:`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The format of the nice part of the collection and operation performed is not straightforward to be printed.&lt;br&gt;
But after digging a while in the lib, I found out that it uses a standard method of the native Node.js library &lt;code&gt;util&lt;/code&gt;, in particular &lt;a href="https://nodejs.org/api/util.html#util_util_inspect_object_options" rel="noopener noreferrer"&gt;util.inspect&lt;/a&gt;, which:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;returns a string representation of object that is intended for debugging. The output of util.inspect may change at any time and should not be depended upon programmatically. Additional options may be passed that alter the result. util.inspect() will use the constructor's name and/or @@toStringTag to make an identifiable tag for an inspected value.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the case of the &lt;code&gt;Mongoose&lt;/code&gt; lib, the object is slightly manipulated to remove new lines and extra white spaces.&lt;br&gt;
The result is similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;util&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inspect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\s{2,}&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So putting altogether, the final configuration of the Mongoose logging is something like that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;mongoose&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;debug&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;collectionName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;methodName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;methodArgs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;msgMapper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;util&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inspect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\s{2,}&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nx"&gt;customLogger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`\x1B[0;36mMongoose:\x1B[0m: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;collectionName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;methodName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;`(&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;methodArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msgMapper&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;, &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;)`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which outputs:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwcazii73woze226incph.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwcazii73woze226incph.png" alt="Log Color" width="800" height="20"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I am happy that I can finally achieve the same log output created by the Mongoose library with any custom logging library I may employ.&lt;/p&gt;

</description>
      <category>node</category>
      <category>logging</category>
      <category>mongodb</category>
      <category>mongoose</category>
    </item>
  </channel>
</rss>
