Repository: open-telemetry/opentelemetry-python-contrib

Issue:
Add `otelTraceSampled` field to LogEntry for OLTP Logging Instrumentation module
Before opening a feature request against this repo, consider whether the feature should/could be implemented in the [other OpenTelemetry client libraries](https://github.com/open-telemetry/). If so, please [open an issue on opentelemetry-specification](https://github.com/open-telemetry/opentelemetry-specification/issues/new) first.

**Is your feature request related to a problem?**
Getting span id and trace id in the log record is a must. Cloud provider libraries, e.g. Google Cloud Logging also provides a `logging.googleapis.com/trace_sampled` field under structured logging, which can be populated using this library. 


**Describe the solution you'd like**
Add a `record.otelTraceSampled` field similar to `record.otelSpanID` and `record.otelTraceID` in the log entry using the `trace_flags` property in `SpanContext`. 

**Describe alternatives you've considered**
Manually injecting the value of `trace_flags` property into the log record by using the current `SpanContext`.




Discussion:
Feel free to send a pull request.
Hi there,

I would like to help out with this feature request. As a contributor, I can see the importance of including the span id and trace id in the log record. I have experience with OpenTelemetry and would be happy to work on implementing this feature.

Please let me know if this issue is still available and if there are any guidelines or resources I should follow while working on it. Thank you!
Hi @msaad777, I’ve made some changes and will be pushing a PR soon. I’ll update here if I’m unable to make a PR. Thanks! 

Resolving patch:
diff --git a/instrumentation/opentelemetry-instrumentation-logging/src/opentelemetry/instrumentation/logging/__init__.py b/instrumentation/opentelemetry-instrumentation-logging/src/opentelemetry/instrumentation/logging/__init__.py
--- a/instrumentation/opentelemetry-instrumentation-logging/src/opentelemetry/instrumentation/logging/__init__.py
+++ b/instrumentation/opentelemetry-instrumentation-logging/src/opentelemetry/instrumentation/logging/__init__.py
@@ -94,6 +94,7 @@ def record_factory(*args, **kwargs):
 
             record.otelSpanID = "0"
             record.otelTraceID = "0"
+            record.otelTraceSampled = False
 
             nonlocal service_name
             if service_name is None:
@@ -113,6 +114,7 @@ def record_factory(*args, **kwargs):
                 if ctx != INVALID_SPAN_CONTEXT:
                     record.otelSpanID = format(ctx.span_id, "016x")
                     record.otelTraceID = format(ctx.trace_id, "032x")
+                    record.otelTraceSampled = ctx.trace_flags.sampled
                     if callable(LoggingInstrumentor._log_hook):
                         try:
                             LoggingInstrumentor._log_hook(  # pylint: disable=E1102
diff --git a/instrumentation/opentelemetry-instrumentation-logging/src/opentelemetry/instrumentation/logging/constants.py b/instrumentation/opentelemetry-instrumentation-logging/src/opentelemetry/instrumentation/logging/constants.py
--- a/instrumentation/opentelemetry-instrumentation-logging/src/opentelemetry/instrumentation/logging/constants.py
+++ b/instrumentation/opentelemetry-instrumentation-logging/src/opentelemetry/instrumentation/logging/constants.py
@@ -12,7 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-DEFAULT_LOGGING_FORMAT = "%(asctime)s %(levelname)s [%(name)s] [%(filename)s:%(lineno)d] [trace_id=%(otelTraceID)s span_id=%(otelSpanID)s resource.service.name=%(otelServiceName)s] - %(message)s"
+DEFAULT_LOGGING_FORMAT = "%(asctime)s %(levelname)s [%(name)s] [%(filename)s:%(lineno)d] [trace_id=%(otelTraceID)s span_id=%(otelSpanID)s resource.service.name=%(otelServiceName)s trace_sampled=%(otelTraceSampled)s] - %(message)s"
 
 
 _MODULE_DOC = """
@@ -27,6 +27,7 @@
 - ``otelSpanID``
 - ``otelTraceID``
 - ``otelServiceName``
+- ``otelTraceSampled``
 
 The integration uses the following logging format by default:
 
@@ -113,7 +114,7 @@
 
 .. code-block::
 
-    %(otelSpanID)s %(otelTraceID)s %(otelServiceName)s
+    %(otelSpanID)s %(otelTraceID)s %(otelServiceName)s %(otelTraceSampled)s
 
 
 


Test patch:
diff --git a/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py b/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py
--- a/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py
+++ b/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py
@@ -62,6 +62,7 @@ def test_trace_context_injection(self):
             self.assertEqual(record.otelSpanID, "0")
             self.assertEqual(record.otelTraceID, "0")
             self.assertEqual(record.otelServiceName, "")
+            self.assertEqual(record.otelTraceSampled, False)
 
 
 def log_hook(span, record):
@@ -82,7 +83,7 @@ def tearDown(self):
         super().tearDown()
         LoggingInstrumentor().uninstrument()
 
-    def assert_trace_context_injected(self, span_id, trace_id):
+    def assert_trace_context_injected(self, span_id, trace_id, trace_sampled):
         with self.caplog.at_level(level=logging.INFO):
             logger = logging.getLogger("test logger")
             logger.info("hello")
@@ -90,16 +91,20 @@ def assert_trace_context_injected(self, span_id, trace_id):
             record = self.caplog.records[0]
             self.assertEqual(record.otelSpanID, span_id)
             self.assertEqual(record.otelTraceID, trace_id)
+            self.assertEqual(record.otelTraceSampled, trace_sampled)
             self.assertEqual(record.otelServiceName, "unknown_service")
 
     def test_trace_context_injection(self):
         with self.tracer.start_as_current_span("s1") as span:
             span_id = format(span.get_span_context().span_id, "016x")
             trace_id = format(span.get_span_context().trace_id, "032x")
-            self.assert_trace_context_injected(span_id, trace_id)
+            trace_sampled = span.get_span_context().trace_flags.sampled
+            self.assert_trace_context_injected(
+                span_id, trace_id, trace_sampled
+            )
 
     def test_trace_context_injection_without_span(self):
-        self.assert_trace_context_injected("0", "0")
+        self.assert_trace_context_injected("0", "0", False)
 
     @mock.patch("logging.basicConfig")
     def test_basic_config_called(self, basic_config_mock):
@@ -163,6 +168,7 @@ def test_log_hook(self):
         with self.tracer.start_as_current_span("s1") as span:
             span_id = format(span.get_span_context().span_id, "016x")
             trace_id = format(span.get_span_context().trace_id, "032x")
+            trace_sampled = span.get_span_context().trace_flags.sampled
             with self.caplog.at_level(level=logging.INFO):
                 logger = logging.getLogger("test logger")
                 logger.info("hello")
@@ -171,6 +177,7 @@ def test_log_hook(self):
                 self.assertEqual(record.otelSpanID, span_id)
                 self.assertEqual(record.otelTraceID, trace_id)
                 self.assertEqual(record.otelServiceName, "unknown_service")
+                self.assertEqual(record.otelTraceSampled, trace_sampled)
                 self.assertEqual(
                     record.custom_user_attribute_from_log_hook, "some-value"
                 )
@@ -179,7 +186,10 @@ def test_uninstrumented(self):
         with self.tracer.start_as_current_span("s1") as span:
             span_id = format(span.get_span_context().span_id, "016x")
             trace_id = format(span.get_span_context().trace_id, "032x")
-            self.assert_trace_context_injected(span_id, trace_id)
+            trace_sampled = span.get_span_context().trace_flags.sampled
+            self.assert_trace_context_injected(
+                span_id, trace_id, trace_sampled
+            )
 
         LoggingInstrumentor().uninstrument()
 
@@ -187,6 +197,7 @@ def test_uninstrumented(self):
         with self.tracer.start_as_current_span("s1") as span:
             span_id = format(span.get_span_context().span_id, "016x")
             trace_id = format(span.get_span_context().trace_id, "032x")
+            trace_sampled = span.get_span_context().trace_flags.sampled
             with self.caplog.at_level(level=logging.INFO):
                 logger = logging.getLogger("test logger")
                 logger.info("hello")
@@ -195,3 +206,4 @@ def test_uninstrumented(self):
                 self.assertFalse(hasattr(record, "otelSpanID"))
                 self.assertFalse(hasattr(record, "otelTraceID"))
                 self.assertFalse(hasattr(record, "otelServiceName"))
+                self.assertFalse(hasattr(record, "otelTraceSampled"))