Repository: googleapis/google-cloud-python

Issue:
Logging: '_BaseEntry' missing fields added to 'LogEntry' in API 
[LogEntry](https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry) defines several additional fields which are dropped by  [_BaseEntry](https://github.com/GoogleCloudPlatform/google-cloud-python/blob/master/logging/google/cloud/logging/entries.py#L51)  

- [LogEntry](https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry)  shows
```
  "receiveTimestamp": string,
  "traceSampled": boolean,
  "sourceLocation": {
    object(LogEntrySourceLocation)
  },
```
but those are not set on init of [entries.py](https://github.com/GoogleCloudPlatform/google-cloud-python/blob/master/logging/google/cloud/logging/entries.py#L87)




Discussion:
`receiveTimestamp` is output-only:  it likely shouldn't be settable by the normal `LogEntry` constructor, but should be captured in `LogEntry.from_api_repr`.  The other two should be settable.

Resolving patch:
diff --git a/logging/google/cloud/logging/_helpers.py b/logging/google/cloud/logging/_helpers.py
--- a/logging/google/cloud/logging/_helpers.py
+++ b/logging/google/cloud/logging/_helpers.py
@@ -16,7 +16,7 @@
 
 import requests
 
-from google.cloud.logging.entries import EmptyEntry
+from google.cloud.logging.entries import LogEntry
 from google.cloud.logging.entries import ProtobufEntry
 from google.cloud.logging.entries import StructEntry
 from google.cloud.logging.entries import TextEntry
@@ -54,7 +54,7 @@ def entry_from_resource(resource, client, loggers):
     if 'protoPayload' in resource:
         return ProtobufEntry.from_api_repr(resource, client, loggers)
 
-    return EmptyEntry.from_api_repr(resource, client, loggers)
+    return LogEntry.from_api_repr(resource, client, loggers)
 
 
 def retrieve_metadata_server(metadata_key):
diff --git a/logging/google/cloud/logging/entries.py b/logging/google/cloud/logging/entries.py
--- a/logging/google/cloud/logging/entries.py
+++ b/logging/google/cloud/logging/entries.py
@@ -14,15 +14,21 @@
 
 """Log entries within the Google Stackdriver Logging API."""
 
+import collections
 import json
 import re
 
-from google.protobuf import any_pb2
+from google.protobuf.any_pb2 import Any
+from google.protobuf.json_format import MessageToDict
 from google.protobuf.json_format import Parse
 
 from google.cloud.logging.resource import Resource
 from google.cloud._helpers import _name_from_project_path
 from google.cloud._helpers import _rfc3339_nanos_to_datetime
+from google.cloud._helpers import _datetime_to_rfc3339
+
+
+_GLOBAL_RESOURCE = Resource(type='global', labels={})
 
 
 _LOGGER_TEMPLATE = re.compile(r"""
@@ -48,31 +54,58 @@ def logger_name_from_path(path):
     return _name_from_project_path(path, None, _LOGGER_TEMPLATE)
 
 
-class _BaseEntry(object):
-    """Base class for TextEntry, StructEntry, ProtobufEntry.
+def _int_or_none(value):
+    """Helper: return an integer or ``None``."""
+    if value is not None:
+        value = int(value)
+    return value
 
-    :type payload: text or dict
-    :param payload: The payload passed as ``textPayload``, ``jsonPayload``,
-                    or ``protoPayload``.
 
-    :type logger: :class:`google.cloud.logging.logger.Logger`
-    :param logger: the logger used to write the entry.
+_LOG_ENTRY_FIELDS = (  # (name, default)
+    ('log_name', None),
+    ('labels', None),
+    ('insert_id', None),
+    ('severity', None),
+    ('http_request', None),
+    ('timestamp', None),
+    ('resource', _GLOBAL_RESOURCE),
+    ('trace', None),
+    ('span_id', None),
+    ('trace_sampled', None),
+    ('source_location', None),
+    ('operation', None),
+    ('logger', None),
+    ('payload', None),
+)
 
-    :type insert_id: text
-    :param insert_id: (optional) the ID used to identify an entry uniquely.
 
-    :type timestamp: :class:`datetime.datetime`
-    :param timestamp: (optional) timestamp for the entry
+_LogEntryTuple = collections.namedtuple(
+        'LogEntry', (field for field, _ in _LOG_ENTRY_FIELDS))
+
+_LogEntryTuple.__new__.__defaults__ = tuple(
+    default for _, default in _LOG_ENTRY_FIELDS)
+
+
+_LOG_ENTRY_PARAM_DOCSTRING = """\
+
+    :type log_name: str
+    :param log_name: the name of the logger used to post the entry.
 
     :type labels: dict
     :param labels: (optional) mapping of labels for the entry
 
+    :type insert_id: text
+    :param insert_id: (optional) the ID used to identify an entry uniquely.
+
     :type severity: str
     :param severity: (optional) severity of event being logged.
 
     :type http_request: dict
     :param http_request: (optional) info about HTTP request associated with
-                         the entry.
+                            the entry.
+
+    :type timestamp: :class:`datetime.datetime`
+    :param timestamp: (optional) timestamp for the entry
 
     :type resource: :class:`~google.cloud.logging.resource.Resource`
     :param resource: (Optional) Monitored resource of the entry
@@ -83,21 +116,43 @@ class _BaseEntry(object):
     :type span_id: str
     :param span_id: (optional) span_id within the trace for the log entry.
                     Specify the trace parameter if span_id is set.
-    """
 
-    def __init__(self, payload, logger, insert_id=None, timestamp=None,
-                 labels=None, severity=None, http_request=None, resource=None,
-                 trace=None, span_id=None):
-        self.payload = payload
-        self.logger = logger
-        self.insert_id = insert_id
-        self.timestamp = timestamp
-        self.labels = labels
-        self.severity = severity
-        self.http_request = http_request
-        self.resource = resource
-        self.trace = trace
-        self.span_id = span_id
+    :type trace_sampled: bool
+    :param trace_sampled: (optional) the sampling decision of the trace
+                          associated with the log entry.
+
+    :type source_location: dict
+    :param source_location: (optional) location in source code from which
+                            the entry was emitted.
+
+    :type operation: dict
+    :param operation: (optional) additional information about a potentially
+                      long-running operation associated with the log entry.
+
+    :type logger: :class:`google.cloud.logging.logger.Logger`
+    :param logger: the logger used to write the entry.
+
+"""
+
+_LOG_ENTRY_SEE_ALSO_DOCSTRING = """\
+
+    See:
+    https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry
+"""
+
+
+class LogEntry(_LogEntryTuple):
+    __doc__ = """
+    Log entry.
+
+    """ + _LOG_ENTRY_PARAM_DOCSTRING + _LOG_ENTRY_SEE_ALSO_DOCSTRING
+
+    received_timestamp = None
+
+    @classmethod
+    def _extract_payload(cls, resource):
+        """Helper for :meth:`from_api_repr`"""
+        return None
 
     @classmethod
     def from_api_repr(cls, resource, client, loggers=None):
@@ -116,8 +171,8 @@ def from_api_repr(cls, resource, client, loggers=None):
             (Optional) A mapping of logger fullnames -> loggers.  If not
             passed, the entry will have a newly-created logger.
 
-        :rtype: :class:`google.cloud.logging.entries._BaseEntry`
-        :returns: Text entry parsed from ``resource``.
+        :rtype: :class:`google.cloud.logging.entries.LogEntry`
+        :returns: Log entry parsed from ``resource``.
         """
         if loggers is None:
             loggers = {}
@@ -126,10 +181,7 @@ def from_api_repr(cls, resource, client, loggers=None):
         if logger is None:
             logger_name = logger_name_from_path(logger_fullname)
             logger = loggers[logger_fullname] = client.logger(logger_name)
-        if cls._PAYLOAD_KEY is not None:
-            payload = resource[cls._PAYLOAD_KEY]
-        else:
-            payload = None
+        payload = cls._extract_payload(resource)
         insert_id = resource.get('insertId')
         timestamp = resource.get('timestamp')
         if timestamp is not None:
@@ -139,99 +191,149 @@ def from_api_repr(cls, resource, client, loggers=None):
         http_request = resource.get('httpRequest')
         trace = resource.get('trace')
         span_id = resource.get('spanId')
+        trace_sampled = resource.get('traceSampled')
+        source_location = resource.get('sourceLocation')
+        if source_location is not None:
+            line = source_location.pop('line', None)
+            source_location['line'] = _int_or_none(line)
+        operation = resource.get('operation')
 
         monitored_resource_dict = resource.get('resource')
         monitored_resource = None
         if monitored_resource_dict is not None:
             monitored_resource = Resource._from_dict(monitored_resource_dict)
 
-        return cls(payload, logger, insert_id=insert_id, timestamp=timestamp,
-                   labels=labels, severity=severity, http_request=http_request,
-                   resource=monitored_resource, trace=trace, span_id=span_id)
-
-
-class EmptyEntry(_BaseEntry):
-    """Entry created with no payload.
-
-    See
-    https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry
-    """
-    _PAYLOAD_KEY = None
-
-
-class TextEntry(_BaseEntry):
-    """Entry created with ``textPayload``.
-
-    See
-    https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry
-    """
-    _PAYLOAD_KEY = 'textPayload'
-
-
-class StructEntry(_BaseEntry):
-    """Entry created with ``jsonPayload``.
-
-    See
-    https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry
-    """
-    _PAYLOAD_KEY = 'jsonPayload'
+        inst = cls(
+            log_name=logger_fullname,
+            insert_id=insert_id,
+            timestamp=timestamp,
+            labels=labels,
+            severity=severity,
+            http_request=http_request,
+            resource=monitored_resource,
+            trace=trace,
+            span_id=span_id,
+            trace_sampled=trace_sampled,
+            source_location=source_location,
+            operation=operation,
+            logger=logger,
+            payload=payload,
+        )
+        received = resource.get('receiveTimestamp')
+        if received is not None:
+            inst.received_timestamp = _rfc3339_nanos_to_datetime(received)
+        return inst
+
+    def to_api_repr(self):
+        """API repr (JSON format) for entry.
+        """
+        info = {}
+        if self.log_name is not None:
+            info['logName'] = self.log_name
+        if self.resource is not None:
+            info['resource'] = self.resource._to_dict()
+        if self.labels is not None:
+            info['labels'] = self.labels
+        if self.insert_id is not None:
+            info['insertId'] = self.insert_id
+        if self.severity is not None:
+            info['severity'] = self.severity
+        if self.http_request is not None:
+            info['httpRequest'] = self.http_request
+        if self.timestamp is not None:
+            info['timestamp'] = _datetime_to_rfc3339(self.timestamp)
+        if self.trace is not None:
+            info['trace'] = self.trace
+        if self.span_id is not None:
+            info['spanId'] = self.span_id
+        if self.trace_sampled is not None:
+            info['traceSampled'] = self.trace_sampled
+        if self.source_location is not None:
+            source_location = self.source_location.copy()
+            source_location['line'] = str(source_location.pop('line', 0))
+            info['sourceLocation'] = source_location
+        if self.operation is not None:
+            info['operation'] = self.operation
+        return info
+
+
+class TextEntry(LogEntry):
+    __doc__ = """
+    Log entry with text payload.
+
+    """ + _LOG_ENTRY_PARAM_DOCSTRING + """
+
+    :type payload: str | unicode
+    :param payload: payload for the log entry.
+    """ + _LOG_ENTRY_SEE_ALSO_DOCSTRING
 
+    @classmethod
+    def _extract_payload(cls, resource):
+        """Helper for :meth:`from_api_repr`"""
+        return resource['textPayload']
 
-class ProtobufEntry(_BaseEntry):
-    """Entry created with ``protoPayload``.
+    def to_api_repr(self):
+        """API repr (JSON format) for entry.
+        """
+        info = super(TextEntry, self).to_api_repr()
+        info['textPayload'] = self.payload
+        return info
 
-    See
-    https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry
 
-    :type payload: str, dict or any_pb2.Any
-    :param payload: The payload passed as ``textPayload``, ``jsonPayload``,
-                    or ``protoPayload``. This also may be passed as a raw
-                    :class:`.any_pb2.Any` if the ``protoPayload`` could
-                    not be deserialized.
+class StructEntry(LogEntry):
+    __doc__ = """
+    Log entry with JSON payload.
 
-    :type logger: :class:`~google.cloud.logging.logger.Logger`
-    :param logger: the logger used to write the entry.
+    """ + _LOG_ENTRY_PARAM_DOCSTRING + """
 
-    :type insert_id: str
-    :param insert_id: (optional) the ID used to identify an entry uniquely.
+    :type payload: dict
+    :param payload: payload for the log entry.
+    """ + _LOG_ENTRY_SEE_ALSO_DOCSTRING
 
-    :type timestamp: :class:`datetime.datetime`
-    :param timestamp: (optional) timestamp for the entry
+    @classmethod
+    def _extract_payload(cls, resource):
+        """Helper for :meth:`from_api_repr`"""
+        return resource['jsonPayload']
 
-    :type labels: dict
-    :param labels: (optional) mapping of labels for the entry
+    def to_api_repr(self):
+        """API repr (JSON format) for entry.
+        """
+        info = super(StructEntry, self).to_api_repr()
+        info['jsonPayload'] = self.payload
+        return info
 
-    :type severity: str
-    :param severity: (optional) severity of event being logged.
 
-    :type http_request: dict
-    :param http_request: (optional) info about HTTP request associated with
-                         the entry
+class ProtobufEntry(LogEntry):
+    __doc__ = """
+    Log entry with protobuf message payload.
 
-    :type resource: :class:`~google.cloud.logging.resource.Resource`
-    :param resource: (Optional) Monitored resource of the entry
+    """ + _LOG_ENTRY_PARAM_DOCSTRING + """
 
-    :type trace: str
-    :param trace: (optional) traceid to apply to the entry.
+    :type payload: protobuf message
+    :param payload: payload for the log entry.
+    """ + _LOG_ENTRY_SEE_ALSO_DOCSTRING
 
-    :type span_id: str
-    :param span_id: (optional) span_id within the trace for the log entry.
-                    Specify the trace parameter if span_id is set.
-    """
-    _PAYLOAD_KEY = 'protoPayload'
-
-    def __init__(self, payload, logger, insert_id=None, timestamp=None,
-                 labels=None, severity=None, http_request=None, resource=None,
-                 trace=None, span_id=None):
-        super(ProtobufEntry, self).__init__(
-            payload, logger, insert_id=insert_id, timestamp=timestamp,
-            labels=labels, severity=severity, http_request=http_request,
-            resource=resource, trace=trace, span_id=span_id)
-        if isinstance(self.payload, any_pb2.Any):
-            self.payload_pb = self.payload
-            self.payload = None
-        else:
-            self.payload_pb = None
+    @classmethod
+    def _extract_payload(cls, resource):
+        """Helper for :meth:`from_api_repr`"""
+        return resource['protoPayload']
+
+    @property
+    def payload_pb(self):
+        if isinstance(self.payload, Any):
+            return self.payload
+
+    @property
+    def payload_json(self):
+        if not isinstance(self.payload, Any):
+            return self.payload
+
+    def to_api_repr(self):
+        """API repr (JSON format) for entry.
+        """
+        info = super(ProtobufEntry, self).to_api_repr()
+        info['protoPayload'] = MessageToDict(self.payload)
+        return info
 
     def parse_message(self, message):
         """Parse payload into a protobuf message.
diff --git a/logging/google/cloud/logging/logger.py b/logging/google/cloud/logging/logger.py
--- a/logging/google/cloud/logging/logger.py
+++ b/logging/google/cloud/logging/logger.py
@@ -14,14 +14,33 @@
 
 """Define API Loggers."""
 
-from google.protobuf.json_format import MessageToDict
-from google.cloud._helpers import _datetime_to_rfc3339
+from google.cloud.logging.entries import LogEntry
+from google.cloud.logging.entries import ProtobufEntry
+from google.cloud.logging.entries import StructEntry
+from google.cloud.logging.entries import TextEntry
 from google.cloud.logging.resource import Resource
 
 
 _GLOBAL_RESOURCE = Resource(type='global', labels={})
 
 
+_OUTBOUND_ENTRY_FIELDS = (  # (name, default)
+    ('type_', None),
+    ('log_name', None),
+    ('payload', None),
+    ('labels', None),
+    ('insert_id', None),
+    ('severity', None),
+    ('http_request', None),
+    ('timestamp', None),
+    ('resource', _GLOBAL_RESOURCE),
+    ('trace', None),
+    ('span_id', None),
+    ('trace_sampled', None),
+    ('source_location', None),
+)
+
+
 class Logger(object):
     """Loggers represent named targets for log entries.
 
@@ -93,104 +112,25 @@ def batch(self, client=None):
         client = self._require_client(client)
         return Batch(self, client)
 
-    def _make_entry_resource(self, text=None, info=None, message=None,
-                             labels=None, insert_id=None, severity=None,
-                             http_request=None, timestamp=None,
-                             resource=_GLOBAL_RESOURCE, trace=None,
-                             span_id=None):
-        """Return a log entry resource of the appropriate type.
-
-        Helper for :meth:`log_text`, :meth:`log_struct`, and :meth:`log_proto`.
-
-        Only one of ``text``, ``info``, or ``message`` should be passed.
-
-        :type text: str
-        :param text: (Optional) text payload
-
-        :type info: dict
-        :param info: (Optional) struct payload
-
-        :type message: :class:`~google.protobuf.message.Message`
-        :param message: (Optional) The protobuf payload to log.
-
-        :type labels: dict
-        :param labels: (Optional) labels passed in to calling method.
-
-        :type insert_id: str
-        :param insert_id: (Optional) unique ID for log entry.
-
-        :type severity: str
-        :param severity: (Optional) severity of event being logged.
-
-        :type http_request: dict
-        :param http_request: (Optional) info about HTTP request associated with
-                             the entry
-
-        :type timestamp: :class:`datetime.datetime`
-        :param timestamp: (Optional) timestamp of event being logged.
-
-        :type resource: :class:`~google.cloud.logging.resource.Resource`
-        :param resource: (Optional) Monitored resource of the entry
-
-        :type trace: str
-        :param trace: (optional) traceid to apply to the entry.
-
-        :type span_id: str
-        :param span_id: (optional) span_id within the trace for the log entry.
-                        Specify the trace parameter if span_id is set.
-
-        :rtype: dict
-        :returns: The JSON resource created.
+    def _do_log(self, client, _entry_class, payload=None, **kw):
+        """Helper for :meth:`log_empty`, :meth:`log_text`, etc.
         """
-        entry = {
-            'logName': self.full_name,
-            'resource': resource._to_dict(),
-        }
-
-        if text is not None:
-            entry['textPayload'] = text
-
-        if info is not None:
-            entry['jsonPayload'] = info
-
-        if message is not None:
-            # NOTE: If ``message`` contains an ``Any`` field with an
-            #       unknown type, this will fail with a ``TypeError``.
-            #       However, since ``message`` will be provided by a user,
-            #       the assumption is that any types needed for the
-            #       protobuf->JSON conversion will be known from already
-            #       imported ``pb2`` modules.
-            entry['protoPayload'] = MessageToDict(message)
-
-        if labels is None:
-            labels = self.labels
-
-        if labels is not None:
-            entry['labels'] = labels
-
-        if insert_id is not None:
-            entry['insertId'] = insert_id
-
-        if severity is not None:
-            entry['severity'] = severity
-
-        if http_request is not None:
-            entry['httpRequest'] = http_request
-
-        if timestamp is not None:
-            entry['timestamp'] = _datetime_to_rfc3339(timestamp)
+        client = self._require_client(client)
 
-        if trace is not None:
-            entry['trace'] = trace
+        # Apply defaults
+        kw['log_name'] = kw.pop('log_name', self.full_name)
+        kw['labels'] = kw.pop('labels', self.labels)
+        kw['resource'] = kw.pop('resource', _GLOBAL_RESOURCE)
 
-        if span_id is not None:
-            entry['spanId'] = span_id
+        if payload is not None:
+            entry = _entry_class(payload=payload, **kw)
+        else:
+            entry = _entry_class(**kw)
 
-        return entry
+        api_repr = entry.to_api_repr()
+        client.logging_api.write_entries([api_repr])
 
-    def log_empty(self, client=None, labels=None, insert_id=None,
-                  severity=None, http_request=None, timestamp=None,
-                  resource=_GLOBAL_RESOURCE, trace=None, span_id=None):
+    def log_empty(self, client=None, **kw):
         """API call:  log an empty message via a POST request
 
         See
@@ -201,43 +141,13 @@ def log_empty(self, client=None, labels=None, insert_id=None,
         :param client: the client to use.  If not passed, falls back to the
                        ``client`` stored on the current logger.
 
-        :type labels: dict
-        :param labels: (optional) mapping of labels for the entry.
-
-        :type insert_id: str
-        :param insert_id: (optional) unique ID for log entry.
-
-        :type severity: str
-        :param severity: (optional) severity of event being logged.
-
-        :type http_request: dict
-        :param http_request: (optional) info about HTTP request associated with
-                             the entry
-
-        :type timestamp: :class:`datetime.datetime`
-        :param timestamp: (optional) timestamp of event being logged.
-
-        :type resource: :class:`~google.cloud.logging.resource.Resource`
-        :param resource: Monitored resource of the entry, defaults
-                         to the global resource type.
-
-        :type trace: str
-        :param trace: (optional) traceid to apply to the entry.
-
-        :type span_id: str
-        :param span_id: (optional) span_id within the trace for the log entry.
-                        Specify the trace parameter if span_id is set.
+        :type kw: dict
+        :param kw: (optional) additional keyword arguments for the entry.
+                   See :class:`~google.cloud.logging.entries.LogEntry`.
         """
-        client = self._require_client(client)
-        entry_resource = self._make_entry_resource(
-            labels=labels, insert_id=insert_id, severity=severity,
-            http_request=http_request, timestamp=timestamp, resource=resource,
-            trace=trace, span_id=span_id)
-        client.logging_api.write_entries([entry_resource])
-
-    def log_text(self, text, client=None, labels=None, insert_id=None,
-                 severity=None, http_request=None, timestamp=None,
-                 resource=_GLOBAL_RESOURCE, trace=None, span_id=None):
+        self._do_log(client, LogEntry, **kw)
+
+    def log_text(self, text, client=None, **kw):
         """API call:  log a text message via a POST request
 
         See
@@ -251,43 +161,13 @@ def log_text(self, text, client=None, labels=None, insert_id=None,
         :param client: the client to use.  If not passed, falls back to the
                        ``client`` stored on the current logger.
 
-        :type labels: dict
-        :param labels: (optional) mapping of labels for the entry.
-
-        :type insert_id: str
-        :param insert_id: (optional) unique ID for log entry.
-
-        :type severity: str
-        :param severity: (optional) severity of event being logged.
-
-        :type http_request: dict
-        :param http_request: (optional) info about HTTP request associated with
-                             the entry
-
-        :type timestamp: :class:`datetime.datetime`
-        :param timestamp: (optional) timestamp of event being logged.
-
-        :type resource: :class:`~google.cloud.logging.resource.Resource`
-        :param resource: Monitored resource of the entry, defaults
-                         to the global resource type.
-
-        :type trace: str
-        :param trace: (optional) traceid to apply to the entry.
-
-        :type span_id: str
-        :param span_id: (optional) span_id within the trace for the log entry.
-                        Specify the trace parameter if span_id is set.
+        :type kw: dict
+        :param kw: (optional) additional keyword arguments for the entry.
+                   See :class:`~google.cloud.logging.entries.LogEntry`.
         """
-        client = self._require_client(client)
-        entry_resource = self._make_entry_resource(
-            text=text, labels=labels, insert_id=insert_id, severity=severity,
-            http_request=http_request, timestamp=timestamp, resource=resource,
-            trace=trace, span_id=span_id)
-        client.logging_api.write_entries([entry_resource])
-
-    def log_struct(self, info, client=None, labels=None, insert_id=None,
-                   severity=None, http_request=None, timestamp=None,
-                   resource=_GLOBAL_RESOURCE, trace=None, span_id=None):
+        self._do_log(client, TextEntry, text, **kw)
+
+    def log_struct(self, info, client=None, **kw):
         """API call:  log a structured message via a POST request
 
         See
@@ -301,43 +181,13 @@ def log_struct(self, info, client=None, labels=None, insert_id=None,
         :param client: the client to use.  If not passed, falls back to the
                        ``client`` stored on the current logger.
 
-        :type labels: dict
-        :param labels: (optional) mapping of labels for the entry.
-
-        :type insert_id: str
-        :param insert_id: (optional) unique ID for log entry.
-
-        :type severity: str
-        :param severity: (optional) severity of event being logged.
-
-        :type http_request: dict
-        :param http_request: (optional) info about HTTP request associated with
-                             the entry.
-
-        :type timestamp: :class:`datetime.datetime`
-        :param timestamp: (optional) timestamp of event being logged.
-
-        :type resource: :class:`~google.cloud.logging.resource.Resource`
-        :param resource: Monitored resource of the entry, defaults
-                         to the global resource type.
-
-        :type trace: str
-        :param trace: (optional) traceid to apply to the entry.
-
-        :type span_id: str
-        :param span_id: (optional) span_id within the trace for the log entry.
-                        Specify the trace parameter if span_id is set.
+        :type kw: dict
+        :param kw: (optional) additional keyword arguments for the entry.
+                   See :class:`~google.cloud.logging.entries.LogEntry`.
         """
-        client = self._require_client(client)
-        entry_resource = self._make_entry_resource(
-            info=info, labels=labels, insert_id=insert_id, severity=severity,
-            http_request=http_request, timestamp=timestamp, resource=resource,
-            trace=trace, span_id=span_id)
-        client.logging_api.write_entries([entry_resource])
-
-    def log_proto(self, message, client=None, labels=None, insert_id=None,
-                  severity=None, http_request=None, timestamp=None,
-                  resource=_GLOBAL_RESOURCE, trace=None, span_id=None):
+        self._do_log(client, StructEntry, info, **kw)
+
+    def log_proto(self, message, client=None, **kw):
         """API call:  log a protobuf message via a POST request
 
         See
@@ -351,39 +201,11 @@ def log_proto(self, message, client=None, labels=None, insert_id=None,
         :param client: the client to use.  If not passed, falls back to the
                        ``client`` stored on the current logger.
 
-        :type labels: dict
-        :param labels: (optional) mapping of labels for the entry.
-
-        :type insert_id: str
-        :param insert_id: (optional) unique ID for log entry.
-
-        :type severity: str
-        :param severity: (optional) severity of event being logged.
-
-        :type http_request: dict
-        :param http_request: (optional) info about HTTP request associated with
-                             the entry.
-
-        :type timestamp: :class:`datetime.datetime`
-        :param timestamp: (optional) timestamp of event being logged.
-
-        :type resource: :class:`~google.cloud.logging.resource.Resource`
-        :param resource: Monitored resource of the entry, defaults
-                         to the global resource type.
-
-        :type trace: str
-        :param trace: (optional) traceid to apply to the entry.
-
-        :type span_id: str
-        :param span_id: (optional) span_id within the trace for the log entry.
-                        Specify the trace parameter if span_id is set.
+        :type kw: dict
+        :param kw: (optional) additional keyword arguments for the entry.
+                   See :class:`~google.cloud.logging.entries.LogEntry`.
         """
-        client = self._require_client(client)
-        entry_resource = self._make_entry_resource(
-            message=message, labels=labels, insert_id=insert_id,
-            severity=severity, http_request=http_request, timestamp=timestamp,
-            resource=resource, trace=trace, span_id=span_id)
-        client.logging_api.write_entries([entry_resource])
+        self._do_log(client, ProtobufEntry, message, **kw)
 
     def delete(self, client=None):
         """API call:  delete all entries in a logger via a DELETE request
@@ -429,8 +251,8 @@ def list_entries(self, projects=None, filter_=None, order_by=None,
                            entries.
 
         :rtype: :class:`~google.api_core.page_iterator.Iterator`
-        :returns: Iterator of :class:`~google.cloud.logging.entries._BaseEntry`
-                  accessible to the current logger.
+        :returns: Iterator of log entries accessible to the current logger.
+                  See :class:`~google.cloud.logging.entries.LogEntry`.
         """
         log_filter = 'logName=%s' % (self.full_name,)
         if filter_ is not None:
@@ -475,131 +297,50 @@ def __exit__(self, exc_type, exc_val, exc_tb):
         if exc_type is None:
             self.commit()
 
-    def log_text(self, text, labels=None, insert_id=None, severity=None,
-                 http_request=None, timestamp=None, resource=_GLOBAL_RESOURCE,
-                 trace=None, span_id=None):
+    def log_empty(self, **kw):
+        """Add a entry without payload to be logged during :meth:`commit`.
+
+        :type kw: dict
+        :param kw: (optional) additional keyword arguments for the entry.
+                   See :class:`~google.cloud.logging.entries.LogEntry`.
+        """
+        self.entries.append(LogEntry(**kw))
+
+    def log_text(self, text, **kw):
         """Add a text entry to be logged during :meth:`commit`.
 
         :type text: str
         :param text: the text entry
 
-        :type labels: dict
-        :param labels: (optional) mapping of labels for the entry.
-
-        :type insert_id: str
-        :param insert_id: (optional) unique ID for log entry.
-
-        :type severity: str
-        :param severity: (optional) severity of event being logged.
-
-        :type http_request: dict
-        :param http_request: (optional) info about HTTP request associated with
-                             the entry.
-
-        :type timestamp: :class:`datetime.datetime`
-        :param timestamp: (optional) timestamp of event being logged.
-
-        :type resource: :class:`~google.cloud.logging.resource.Resource`
-        :param resource: (Optional) Monitored resource of the entry. Defaults
-                         to the global resource type. If set to None, the
-                         resource of the batch is used for this entry. If
-                         both this resource and the Batch resource are None,
-                         the API will return an error.
-
-        :type trace: str
-        :param trace: (optional) traceid to apply to the entry.
-
-        :type span_id: str
-        :param span_id: (optional) span_id within the trace for the log entry.
-                        Specify the trace parameter if span_id is set.
+        :type kw: dict
+        :param kw: (optional) additional keyword arguments for the entry.
+                   See :class:`~google.cloud.logging.entries.LogEntry`.
         """
-        self.entries.append(
-            ('text', text, labels, insert_id, severity, http_request,
-             timestamp, resource, trace, span_id))
+        self.entries.append(TextEntry(payload=text, **kw))
 
-    def log_struct(self, info, labels=None, insert_id=None, severity=None,
-                   http_request=None, timestamp=None,
-                   resource=_GLOBAL_RESOURCE, trace=None, span_id=None):
+    def log_struct(self, info, **kw):
         """Add a struct entry to be logged during :meth:`commit`.
 
         :type info: dict
         :param info: the struct entry
 
-        :type labels: dict
-        :param labels: (optional) mapping of labels for the entry.
-
-        :type insert_id: str
-        :param insert_id: (optional) unique ID for log entry.
-
-        :type severity: str
-        :param severity: (optional) severity of event being logged.
-
-        :type http_request: dict
-        :param http_request: (optional) info about HTTP request associated with
-                             the entry.
-
-        :type timestamp: :class:`datetime.datetime`
-        :param timestamp: (optional) timestamp of event being logged.
-
-        :type resource: :class:`~google.cloud.logging.resource.Resource`
-        :param resource: (Optional) Monitored resource of the entry. Defaults
-                         to the global resource type. If set to None, the
-                         resource of the batch is used for this entry. If
-                         both this resource and the Batch resource are None,
-                         the API will return an error.
-
-        :type trace: str
-        :param trace: (optional) traceid to apply to the entry.
-
-        :type span_id: str
-        :param span_id: (optional) span_id within the trace for the log entry.
-                        Specify the trace parameter if span_id is set.
+        :type kw: dict
+        :param kw: (optional) additional keyword arguments for the entry.
+                   See :class:`~google.cloud.logging.entries.LogEntry`.
         """
-        self.entries.append(
-            ('struct', info, labels, insert_id, severity, http_request,
-             timestamp, resource, trace, span_id))
+        self.entries.append(StructEntry(payload=info, **kw))
 
-    def log_proto(self, message, labels=None, insert_id=None, severity=None,
-                  http_request=None, timestamp=None,
-                  resource=_GLOBAL_RESOURCE, trace=None, span_id=None):
+    def log_proto(self, message, **kw):
         """Add a protobuf entry to be logged during :meth:`commit`.
 
         :type message: protobuf message
         :param message: the protobuf entry
 
-        :type labels: dict
-        :param labels: (optional) mapping of labels for the entry.
-
-        :type insert_id: str
-        :param insert_id: (optional) unique ID for log entry.
-
-        :type severity: str
-        :param severity: (optional) severity of event being logged.
-
-        :type http_request: dict
-        :param http_request: (optional) info about HTTP request associated with
-                             the entry.
-
-        :type timestamp: :class:`datetime.datetime`
-        :param timestamp: (optional) timestamp of event being logged.
-
-        :type resource: :class:`~google.cloud.logging.resource.Resource`
-        :param resource: (Optional) Monitored resource of the entry. Defaults
-                         to the global resource type. If set to None, the
-                         resource of the batch is used for this entry. If
-                         both this resource and the Batch resource are None,
-                         the API will return an error.
-
-        :type trace: str
-        :param trace: (optional) traceid to apply to the entry.
-
-        :type span_id: str
-        :param span_id: (optional) span_id within the trace for the log entry.
-                        Specify the trace parameter if span_id is set.
+        :type kw: dict
+        :param kw: (optional) additional keyword arguments for the entry.
+                   See :class:`~google.cloud.logging.entries.LogEntry`.
         """
-        self.entries.append(
-            ('proto', message, labels, insert_id, severity, http_request,
-             timestamp, resource, trace, span_id))
+        self.entries.append(ProtobufEntry(payload=message, **kw))
 
     def commit(self, client=None):
         """Send saved log entries as a single API call.
@@ -618,43 +359,11 @@ def commit(self, client=None):
 
         if self.resource is not None:
             kwargs['resource'] = self.resource._to_dict()
+
         if self.logger.labels is not None:
             kwargs['labels'] = self.logger.labels
 
-        entries = []
-        for (entry_type, entry, labels, iid, severity, http_req,
-             timestamp, resource, trace, span_id) in self.entries:
-            if entry_type == 'text':
-                info = {'textPayload': entry}
-            elif entry_type == 'struct':
-                info = {'jsonPayload': entry}
-            elif entry_type == 'proto':
-                # NOTE: If ``entry`` contains an ``Any`` field with an
-                #       unknown type, this will fail with a ``TypeError``.
-                #       However, since ``entry`` was provided by a user in
-                #       ``Batch.log_proto``, the assumption is that any types
-                #       needed for the protobuf->JSON conversion will be known
-                #       from already imported ``pb2`` modules.
-                info = {'protoPayload': MessageToDict(entry)}
-            else:
-                raise ValueError('Unknown entry type: %s' % (entry_type,))
-            if resource is not None:
-                info['resource'] = resource._to_dict()
-            if labels is not None:
-                info['labels'] = labels
-            if iid is not None:
-                info['insertId'] = iid
-            if severity is not None:
-                info['severity'] = severity
-            if http_req is not None:
-                info['httpRequest'] = http_req
-            if timestamp is not None:
-                info['timestamp'] = _datetime_to_rfc3339(timestamp)
-            if trace is not None:
-                info['trace'] = trace
-            if span_id is not None:
-                info['spanId'] = span_id
-            entries.append(info)
+        entries = [entry.to_api_repr() for entry in self.entries]
 
         client.logging_api.write_entries(entries, **kwargs)
         del self.entries[:]


Test patch:
diff --git a/logging/tests/system/test_system.py b/logging/tests/system/test_system.py
--- a/logging/tests/system/test_system.py
+++ b/logging/tests/system/test_system.py
@@ -140,12 +140,12 @@ def test_list_entry_with_unregistered(self):
 
         self.assertIsInstance(protobuf_entry, entries.ProtobufEntry)
         if Config.CLIENT._use_grpc:
-            self.assertIsNone(protobuf_entry.payload)
+            self.assertIsNone(protobuf_entry.payload_json)
             self.assertIsInstance(protobuf_entry.payload_pb, any_pb2.Any)
             self.assertEqual(protobuf_entry.payload_pb.type_url, type_url)
         else:
             self.assertIsNone(protobuf_entry.payload_pb)
-            self.assertEqual(protobuf_entry.payload['@type'], type_url)
+            self.assertEqual(protobuf_entry.payload_json['@type'], type_url)
 
     def test_log_text(self):
         TEXT_PAYLOAD = 'System test: test_log_text'
@@ -157,6 +157,8 @@ def test_log_text(self):
         self.assertEqual(entries[0].payload, TEXT_PAYLOAD)
 
     def test_log_text_with_timestamp(self):
+        import datetime
+
         text_payload = 'System test: test_log_text_with_timestamp'
         logger = Config.CLIENT.logger(self._logger_name('log_text_ts'))
         now = datetime.datetime.utcnow()
@@ -168,6 +170,7 @@ def test_log_text_with_timestamp(self):
         self.assertEqual(len(entries), 1)
         self.assertEqual(entries[0].payload, text_payload)
         self.assertEqual(entries[0].timestamp, now.replace(tzinfo=UTC))
+        self.assertIsInstance(entries[0].received_timestamp, datetime.datetime)
 
     def test_log_text_with_resource(self):
         text_payload = 'System test: test_log_text_with_timestamp'
diff --git a/logging/tests/unit/test__helpers.py b/logging/tests/unit/test__helpers.py
--- a/logging/tests/unit/test__helpers.py
+++ b/logging/tests/unit/test__helpers.py
@@ -44,7 +44,7 @@ def _payload_helper(self, key, class_name):
         self.assertEqual(mock_class.called, (resource, client, loggers))
 
     def test_wo_payload(self):
-        self._payload_helper(None, 'EmptyEntry')
+        self._payload_helper(None, 'LogEntry')
 
     def test_text_payload(self):
         self._payload_helper('textPayload', 'TextEntry')
diff --git a/logging/tests/unit/test_entries.py b/logging/tests/unit/test_entries.py
--- a/logging/tests/unit/test_entries.py
+++ b/logging/tests/unit/test_entries.py
@@ -39,43 +39,62 @@ def test_w_name_w_all_extras(self):
         self.assertEqual(logger_name, LOGGER_NAME)
 
 
-class Test_BaseEntry(unittest.TestCase):
+class Test__int_or_none(unittest.TestCase):
+
+    def _call_fut(self, value):
+        from google.cloud.logging.entries import _int_or_none
+
+        return _int_or_none(value)
+
+    def test_w_none(self):
+        self.assertIsNone(self._call_fut(None))
+
+    def test_w_int(self):
+        self.assertEqual(self._call_fut(123), 123)
+
+    def test_w_str(self):
+        self.assertEqual(self._call_fut('123'), 123)
+
+
+class TestLogEntry(unittest.TestCase):
 
     PROJECT = 'PROJECT'
     LOGGER_NAME = 'LOGGER_NAME'
 
     @staticmethod
-    def _get_target_class(payload_key='dummyPayload'):
-        from google.cloud.logging.entries import _BaseEntry
-
-        class _Dummy(_BaseEntry):
-            _PAYLOAD_KEY = payload_key
+    def _get_target_class():
+        from google.cloud.logging.entries import LogEntry
 
-        return _Dummy
+        return LogEntry
 
     def _make_one(self, *args, **kw):
         return self._get_target_class()(*args, **kw)
 
     def test_ctor_defaults(self):
-        PAYLOAD = 'PAYLOAD'
-        logger = _Logger(self.LOGGER_NAME, self.PROJECT)
-        entry = self._make_one(PAYLOAD, logger)
-        self.assertEqual(entry.payload, PAYLOAD)
-        self.assertIs(entry.logger, logger)
-        self.assertIsNone(entry.insert_id)
-        self.assertIsNone(entry.timestamp)
+        from google.cloud.logging.entries import _GLOBAL_RESOURCE
+
+        entry = self._make_one()
+
+        self.assertIsNone(entry.log_name)
+        self.assertIsNone(entry.logger)
         self.assertIsNone(entry.labels)
+        self.assertIsNone(entry.insert_id)
         self.assertIsNone(entry.severity)
         self.assertIsNone(entry.http_request)
-        self.assertIsNone(entry.resource)
+        self.assertIsNone(entry.timestamp)
+        self.assertIs(entry.resource, _GLOBAL_RESOURCE)
         self.assertIsNone(entry.trace)
         self.assertIsNone(entry.span_id)
+        self.assertIsNone(entry.trace_sampled)
+        self.assertIsNone(entry.source_location)
+        self.assertIsNone(entry.operation)
+        self.assertIsNone(entry.payload)
 
     def test_ctor_explicit(self):
         import datetime
         from google.cloud.logging.resource import Resource
 
-        PAYLOAD = 'PAYLOAD'
+        LOG_NAME = 'projects/%s/logs/%s' % (self.PROJECT, self.LOGGER_NAME)
         IID = 'IID'
         TIMESTAMP = datetime.datetime.now()
         LABELS = {'foo': 'bar', 'baz': 'qux'}
@@ -91,18 +110,41 @@ def test_ctor_explicit(self):
         resource = Resource(type='global', labels={})
         TRACE = '12345678-1234-5678-1234-567812345678'
         SPANID = '000000000000004a'
-
+        FILE = 'my_file.py'
+        LINE_NO = 123
+        FUNCTION = 'my_function'
+        SOURCE_LOCATION = {
+            'file': FILE,
+            'line': LINE_NO,
+            'function': FUNCTION,
+        }
+        OP_ID = 'OP_ID'
+        PRODUCER = 'PRODUCER'
+        OPERATION = {
+            'id': OP_ID,
+            'producer': PRODUCER,
+            'first': True,
+            'last': False,
+        }
         logger = _Logger(self.LOGGER_NAME, self.PROJECT)
-        entry = self._make_one(PAYLOAD, logger,
-                               insert_id=IID,
-                               timestamp=TIMESTAMP,
-                               labels=LABELS,
-                               severity=SEVERITY,
-                               http_request=REQUEST,
-                               resource=resource,
-                               trace=TRACE,
-                               span_id=SPANID)
-        self.assertEqual(entry.payload, PAYLOAD)
+
+        entry = self._make_one(
+            log_name=LOG_NAME,
+            logger=logger,
+            insert_id=IID,
+            timestamp=TIMESTAMP,
+            labels=LABELS,
+            severity=SEVERITY,
+            http_request=REQUEST,
+            resource=resource,
+            trace=TRACE,
+            span_id=SPANID,
+            trace_sampled=True,
+            source_location=SOURCE_LOCATION,
+            operation=OPERATION,
+        )
+
+        self.assertEqual(entry.log_name, LOG_NAME)
         self.assertIs(entry.logger, logger)
         self.assertEqual(entry.insert_id, IID)
         self.assertEqual(entry.timestamp, TIMESTAMP)
@@ -114,26 +156,41 @@ def test_ctor_explicit(self):
         self.assertEqual(entry.resource, resource)
         self.assertEqual(entry.trace, TRACE)
         self.assertEqual(entry.span_id, SPANID)
+        self.assertTrue(entry.trace_sampled)
+
+        source_location = entry.source_location
+        self.assertEqual(source_location['file'], FILE)
+        self.assertEqual(source_location['line'], LINE_NO)
+        self.assertEqual(source_location['function'], FUNCTION)
+
+        self.assertEqual(entry.operation, OPERATION)
+        self.assertIsNone(entry.payload)
 
-    def test_from_api_repr_no_payload_missing_data_no_loggers(self):
+    def test_from_api_repr_missing_data_no_loggers(self):
         client = _Client(self.PROJECT)
         LOG_NAME = 'projects/%s/logs/%s' % (self.PROJECT, self.LOGGER_NAME)
         API_REPR = {
             'logName': LOG_NAME,
         }
-        klass = self._get_target_class(payload_key=None)
+        klass = self._get_target_class()
+
         entry = klass.from_api_repr(API_REPR, client)
-        self.assertIsNone(entry.payload)
+
+        self.assertEqual(entry.log_name, LOG_NAME)
+        logger = entry.logger
+        self.assertIsInstance(logger, _Logger)
+        self.assertEqual(logger.name, self.LOGGER_NAME)
         self.assertIsNone(entry.insert_id)
         self.assertIsNone(entry.timestamp)
         self.assertIsNone(entry.severity)
         self.assertIsNone(entry.http_request)
         self.assertIsNone(entry.trace)
         self.assertIsNone(entry.span_id)
-        logger = entry.logger
-        self.assertIsInstance(logger, _Logger)
+        self.assertIsNone(entry.trace_sampled)
+        self.assertIsNone(entry.source_location)
+        self.assertIsNone(entry.operation)
         self.assertIs(logger.client, client)
-        self.assertEqual(logger.name, self.LOGGER_NAME)
+        self.assertIsNone(entry.payload)
 
     def test_from_api_repr_w_loggers_no_logger_match(self):
         from datetime import datetime
@@ -142,7 +199,6 @@ def test_from_api_repr_w_loggers_no_logger_match(self):
 
         klass = self._get_target_class()
         client = _Client(self.PROJECT)
-        PAYLOAD = 'PAYLOAD'
         SEVERITY = 'CRITICAL'
         IID = 'IID'
         NOW = datetime.utcnow().replace(tzinfo=UTC)
@@ -164,8 +220,23 @@ def test_from_api_repr_w_loggers_no_logger_match(self):
         STATUS = '500'
         TRACE = '12345678-1234-5678-1234-567812345678'
         SPANID = '000000000000004a'
+        FILE = 'my_file.py'
+        LINE_NO = 123
+        FUNCTION = 'my_function'
+        SOURCE_LOCATION = {
+            'file': FILE,
+            'line': str(LINE_NO),
+            'function': FUNCTION,
+        }
+        OP_ID = 'OP_ID'
+        PRODUCER = 'PRODUCER'
+        OPERATION = {
+            'id': OP_ID,
+            'producer': PRODUCER,
+            'first': True,
+            'last': False,
+        }
         API_REPR = {
-            'dummyPayload': PAYLOAD,
             'logName': LOG_NAME,
             'insertId': IID,
             'timestamp': TIMESTAMP,
@@ -178,60 +249,428 @@ def test_from_api_repr_w_loggers_no_logger_match(self):
             },
             'resource': RESOURCE._to_dict(),
             'trace': TRACE,
-            'spanId': SPANID
+            'spanId': SPANID,
+            'traceSampled': True,
+            'sourceLocation': SOURCE_LOCATION,
+            'operation': OPERATION,
         }
         loggers = {}
+
         entry = klass.from_api_repr(API_REPR, client, loggers=loggers)
-        self.assertEqual(entry.payload, PAYLOAD)
+
+        self.assertEqual(entry.log_name, LOG_NAME)
+        logger = entry.logger
+        self.assertIsInstance(logger, _Logger)
+        self.assertEqual(logger.name, self.LOGGER_NAME)
         self.assertEqual(entry.insert_id, IID)
         self.assertEqual(entry.timestamp, NOW)
+        self.assertIsNone(entry.received_timestamp)
         self.assertEqual(entry.labels, LABELS)
         self.assertEqual(entry.severity, SEVERITY)
         self.assertEqual(entry.http_request['requestMethod'], METHOD)
         self.assertEqual(entry.http_request['requestUrl'], URI)
         self.assertEqual(entry.http_request['status'], STATUS)
-        logger = entry.logger
-        self.assertIsInstance(logger, _Logger)
         self.assertIs(logger.client, client)
         self.assertEqual(logger.name, self.LOGGER_NAME)
         self.assertEqual(loggers, {LOG_NAME: logger})
         self.assertEqual(entry.resource, RESOURCE)
         self.assertEqual(entry.trace, TRACE)
         self.assertEqual(entry.span_id, SPANID)
+        self.assertTrue(entry.trace_sampled)
+
+        source_location = entry.source_location
+        self.assertEqual(source_location['file'], FILE)
+        self.assertEqual(source_location['line'], LINE_NO)
+        self.assertEqual(source_location['function'], FUNCTION)
+
+        self.assertEqual(entry.operation, OPERATION)
+        self.assertIsNone(entry.payload)
 
     def test_from_api_repr_w_loggers_w_logger_match(self):
         from datetime import datetime
+        from datetime import timedelta
         from google.cloud._helpers import UTC
 
         client = _Client(self.PROJECT)
-        PAYLOAD = 'PAYLOAD'
         IID = 'IID'
         NOW = datetime.utcnow().replace(tzinfo=UTC)
+        LATER = NOW + timedelta(seconds=1)
         TIMESTAMP = _datetime_to_rfc3339_w_nanos(NOW)
+        RECEIVED = _datetime_to_rfc3339_w_nanos(LATER)
         LOG_NAME = 'projects/%s/logs/%s' % (self.PROJECT, self.LOGGER_NAME)
         LABELS = {'foo': 'bar', 'baz': 'qux'}
         TRACE = '12345678-1234-5678-1234-567812345678'
         SPANID = '000000000000004a'
+        FILE = 'my_file.py'
+        LINE_NO = 123
+        FUNCTION = 'my_function'
+        SOURCE_LOCATION = {
+            'file': FILE,
+            'line': str(LINE_NO),
+            'function': FUNCTION,
+        }
+        OP_ID = 'OP_ID'
+        PRODUCER = 'PRODUCER'
+        OPERATION = {
+            'id': OP_ID,
+            'producer': PRODUCER,
+            'first': True,
+            'last': False,
+        }
         API_REPR = {
-            'dummyPayload': PAYLOAD,
             'logName': LOG_NAME,
             'insertId': IID,
             'timestamp': TIMESTAMP,
+            'receiveTimestamp': RECEIVED,
             'labels': LABELS,
             'trace': TRACE,
-            'spanId': SPANID
+            'spanId': SPANID,
+            'traceSampled': True,
+            'sourceLocation': SOURCE_LOCATION,
+            'operation': OPERATION,
         }
         LOGGER = object()
         loggers = {LOG_NAME: LOGGER}
         klass = self._get_target_class()
+
         entry = klass.from_api_repr(API_REPR, client, loggers=loggers)
-        self.assertEqual(entry.payload, PAYLOAD)
+
+        self.assertEqual(entry.log_name, LOG_NAME)
+        self.assertIs(entry.logger, LOGGER)
         self.assertEqual(entry.insert_id, IID)
         self.assertEqual(entry.timestamp, NOW)
+        self.assertEqual(entry.received_timestamp, LATER)
         self.assertEqual(entry.labels, LABELS)
         self.assertEqual(entry.trace, TRACE)
         self.assertEqual(entry.span_id, SPANID)
-        self.assertIs(entry.logger, LOGGER)
+        self.assertTrue(entry.trace_sampled)
+
+        source_location = entry.source_location
+        self.assertEqual(source_location['file'], FILE)
+        self.assertEqual(source_location['line'], LINE_NO)
+        self.assertEqual(source_location['function'], FUNCTION)
+
+        self.assertEqual(entry.operation, OPERATION)
+        self.assertIsNone(entry.payload)
+
+    def test_to_api_repr_w_source_location_no_line(self):
+        from google.cloud.logging.logger import _GLOBAL_RESOURCE
+
+        LOG_NAME = 'test.log'
+        FILE = 'my_file.py'
+        FUNCTION = 'my_function'
+        SOURCE_LOCATION = {
+            'file': FILE,
+            'function': FUNCTION,
+        }
+        entry = self._make_one(
+            log_name=LOG_NAME, source_location=SOURCE_LOCATION)
+        expected = {
+            'logName': LOG_NAME,
+            'resource': _GLOBAL_RESOURCE._to_dict(),
+            'sourceLocation': {
+                'file': FILE,
+                'line': '0',
+                'function': FUNCTION,
+            }
+        }
+        self.assertEqual(entry.to_api_repr(), expected)
+
+    def test_to_api_repr_explicit(self):
+        import datetime
+        from google.cloud.logging.resource import Resource
+        from google.cloud._helpers import _datetime_to_rfc3339
+
+        LOG_NAME = 'test.log'
+        LABELS = {'foo': 'bar', 'baz': 'qux'}
+        IID = 'IID'
+        SEVERITY = 'CRITICAL'
+        METHOD = 'POST'
+        URI = 'https://api.example.com/endpoint'
+        STATUS = '500'
+        REQUEST = {
+            'requestMethod': METHOD,
+            'requestUrl': URI,
+            'status': STATUS,
+        }
+        TIMESTAMP = datetime.datetime(2016, 12, 31, 0, 1, 2, 999999)
+        RESOURCE = Resource(
+            type='gae_app',
+            labels={
+                'module_id': 'default',
+                'version_id': 'test'
+            }
+        )
+        TRACE = '12345678-1234-5678-1234-567812345678'
+        SPANID = '000000000000004a'
+        FILE = 'my_file.py'
+        LINE = 123
+        FUNCTION = 'my_function'
+        SOURCE_LOCATION = {
+            'file': FILE,
+            'line': LINE,
+            'function': FUNCTION,
+        }
+        OP_ID = 'OP_ID'
+        PRODUCER = 'PRODUCER'
+        OPERATION = {
+            'id': OP_ID,
+            'producer': PRODUCER,
+            'first': True,
+            'last': False,
+        }
+        expected = {
+            'logName': LOG_NAME,
+            'labels': LABELS,
+            'insertId': IID,
+            'severity': SEVERITY,
+            'httpRequest': REQUEST,
+            'timestamp': _datetime_to_rfc3339(TIMESTAMP),
+            'resource': RESOURCE._to_dict(),
+            'trace': TRACE,
+            'spanId': SPANID,
+            'traceSampled': True,
+            'sourceLocation': {
+                'file': FILE,
+                'line': str(LINE),
+                'function': FUNCTION,
+            },
+            'operation': OPERATION,
+        }
+        entry = self._make_one(
+            log_name=LOG_NAME,
+            labels=LABELS,
+            insert_id=IID,
+            severity=SEVERITY,
+            http_request=REQUEST,
+            timestamp=TIMESTAMP,
+            resource=RESOURCE,
+            trace=TRACE,
+            span_id=SPANID,
+            trace_sampled=True,
+            source_location=SOURCE_LOCATION,
+            operation=OPERATION,
+        )
+
+        self.assertEqual(entry.to_api_repr(), expected)
+
+
+class TestTextEntry(unittest.TestCase):
+
+    PROJECT = 'PROJECT'
+    LOGGER_NAME = 'LOGGER_NAME'
+
+    @staticmethod
+    def _get_target_class():
+        from google.cloud.logging.entries import TextEntry
+
+        return TextEntry
+
+    def _make_one(self, *args, **kw):
+        return self._get_target_class()(*args, **kw)
+
+    def test_to_api_repr_defaults(self):
+        from google.cloud.logging.logger import _GLOBAL_RESOURCE
+
+        LOG_NAME = 'test.log'
+        TEXT = 'TESTING'
+        entry = self._make_one(log_name=LOG_NAME, payload=TEXT)
+        expected = {
+            'logName': LOG_NAME,
+            'textPayload': TEXT,
+            'resource': _GLOBAL_RESOURCE._to_dict(),
+        }
+        self.assertEqual(entry.to_api_repr(), expected)
+
+    def test_to_api_repr_explicit(self):
+        import datetime
+        from google.cloud.logging.resource import Resource
+        from google.cloud._helpers import _datetime_to_rfc3339
+
+        LOG_NAME = 'test.log'
+        TEXT = 'This is the entry text'
+        LABELS = {'foo': 'bar', 'baz': 'qux'}
+        IID = 'IID'
+        SEVERITY = 'CRITICAL'
+        METHOD = 'POST'
+        URI = 'https://api.example.com/endpoint'
+        STATUS = '500'
+        REQUEST = {
+            'requestMethod': METHOD,
+            'requestUrl': URI,
+            'status': STATUS,
+        }
+        TIMESTAMP = datetime.datetime(2016, 12, 31, 0, 1, 2, 999999)
+        RESOURCE = Resource(
+            type='gae_app',
+            labels={
+                'module_id': 'default',
+                'version_id': 'test'
+            }
+        )
+        TRACE = '12345678-1234-5678-1234-567812345678'
+        SPANID = '000000000000004a'
+        FILE = 'my_file.py'
+        LINE = 123
+        FUNCTION = 'my_function'
+        SOURCE_LOCATION = {
+            'file': FILE,
+            'line': LINE,
+            'function': FUNCTION,
+        }
+        OP_ID = 'OP_ID'
+        PRODUCER = 'PRODUCER'
+        OPERATION = {
+            'id': OP_ID,
+            'producer': PRODUCER,
+            'first': True,
+            'last': False,
+        }
+        expected = {
+            'logName': LOG_NAME,
+            'textPayload': TEXT,
+            'labels': LABELS,
+            'insertId': IID,
+            'severity': SEVERITY,
+            'httpRequest': REQUEST,
+            'timestamp': _datetime_to_rfc3339(TIMESTAMP),
+            'resource': RESOURCE._to_dict(),
+            'trace': TRACE,
+            'spanId': SPANID,
+            'traceSampled': True,
+            'sourceLocation': {
+                'file': FILE,
+                'line': str(LINE),
+                'function': FUNCTION,
+            },
+            'operation': OPERATION,
+        }
+        entry = self._make_one(
+            log_name=LOG_NAME,
+            payload=TEXT,
+            labels=LABELS,
+            insert_id=IID,
+            severity=SEVERITY,
+            http_request=REQUEST,
+            timestamp=TIMESTAMP,
+            resource=RESOURCE,
+            trace=TRACE,
+            span_id=SPANID,
+            trace_sampled=True,
+            source_location=SOURCE_LOCATION,
+            operation=OPERATION,
+        )
+
+        self.assertEqual(entry.to_api_repr(), expected)
+
+
+class TestStructEntry(unittest.TestCase):
+
+    PROJECT = 'PROJECT'
+    LOGGER_NAME = 'LOGGER_NAME'
+
+    @staticmethod
+    def _get_target_class():
+        from google.cloud.logging.entries import StructEntry
+
+        return StructEntry
+
+    def _make_one(self, *args, **kw):
+        return self._get_target_class()(*args, **kw)
+
+    def test_to_api_repr_defaults(self):
+        from google.cloud.logging.logger import _GLOBAL_RESOURCE
+
+        LOG_NAME = 'test.log'
+        JSON_PAYLOAD = {'key': 'value'}
+        entry = self._make_one(log_name=LOG_NAME, payload=JSON_PAYLOAD)
+        expected = {
+            'logName': LOG_NAME,
+            'jsonPayload': JSON_PAYLOAD,
+            'resource': _GLOBAL_RESOURCE._to_dict(),
+        }
+        self.assertEqual(entry.to_api_repr(), expected)
+
+    def test_to_api_repr_explicit(self):
+        import datetime
+        from google.cloud.logging.resource import Resource
+        from google.cloud._helpers import _datetime_to_rfc3339
+
+        LOG_NAME = 'test.log'
+        JSON_PAYLOAD = {'key': 'value'}
+        LABELS = {'foo': 'bar', 'baz': 'qux'}
+        IID = 'IID'
+        SEVERITY = 'CRITICAL'
+        METHOD = 'POST'
+        URI = 'https://api.example.com/endpoint'
+        STATUS = '500'
+        REQUEST = {
+            'requestMethod': METHOD,
+            'requestUrl': URI,
+            'status': STATUS,
+        }
+        TIMESTAMP = datetime.datetime(2016, 12, 31, 0, 1, 2, 999999)
+        RESOURCE = Resource(
+            type='gae_app',
+            labels={
+                'module_id': 'default',
+                'version_id': 'test'
+            }
+        )
+        TRACE = '12345678-1234-5678-1234-567812345678'
+        SPANID = '000000000000004a'
+        FILE = 'my_file.py'
+        LINE = 123
+        FUNCTION = 'my_function'
+        SOURCE_LOCATION = {
+            'file': FILE,
+            'line': LINE,
+            'function': FUNCTION,
+        }
+        OP_ID = 'OP_ID'
+        PRODUCER = 'PRODUCER'
+        OPERATION = {
+            'id': OP_ID,
+            'producer': PRODUCER,
+            'first': True,
+            'last': False,
+        }
+        expected = {
+            'logName': LOG_NAME,
+            'jsonPayload': JSON_PAYLOAD,
+            'labels': LABELS,
+            'insertId': IID,
+            'severity': SEVERITY,
+            'httpRequest': REQUEST,
+            'timestamp': _datetime_to_rfc3339(TIMESTAMP),
+            'resource': RESOURCE._to_dict(),
+            'trace': TRACE,
+            'spanId': SPANID,
+            'traceSampled': True,
+            'sourceLocation': {
+                'file': FILE,
+                'line': str(LINE),
+                'function': FUNCTION,
+            },
+            'operation': OPERATION,
+        }
+        entry = self._make_one(
+            log_name=LOG_NAME,
+            payload=JSON_PAYLOAD,
+            labels=LABELS,
+            insert_id=IID,
+            severity=SEVERITY,
+            http_request=REQUEST,
+            timestamp=TIMESTAMP,
+            resource=RESOURCE,
+            trace=TRACE,
+            span_id=SPANID,
+            trace_sampled=True,
+            source_location=SOURCE_LOCATION,
+            operation=OPERATION,
+        )
+
+        self.assertEqual(entry.to_api_repr(), expected)
 
 
 class TestProtobufEntry(unittest.TestCase):
@@ -250,9 +689,12 @@ def _make_one(self, *args, **kw):
 
     def test_constructor_basic(self):
         payload = {'foo': 'bar'}
-        pb_entry = self._make_one(payload, mock.sentinel.logger)
-        self.assertEqual(pb_entry.payload, payload)
+
+        pb_entry = self._make_one(payload=payload, logger=mock.sentinel.logger)
+
+        self.assertIs(pb_entry.payload, payload)
         self.assertIsNone(pb_entry.payload_pb)
+        self.assertIs(pb_entry.payload_json, payload)
         self.assertIs(pb_entry.logger, mock.sentinel.logger)
         self.assertIsNone(pb_entry.insert_id)
         self.assertIsNone(pb_entry.timestamp)
@@ -261,14 +703,19 @@ def test_constructor_basic(self):
         self.assertIsNone(pb_entry.http_request)
         self.assertIsNone(pb_entry.trace)
         self.assertIsNone(pb_entry.span_id)
+        self.assertIsNone(pb_entry.trace_sampled)
+        self.assertIsNone(pb_entry.source_location)
 
     def test_constructor_with_any(self):
         from google.protobuf.any_pb2 import Any
 
         payload = Any()
-        pb_entry = self._make_one(payload, mock.sentinel.logger)
+
+        pb_entry = self._make_one(payload=payload, logger=mock.sentinel.logger)
+
+        self.assertIs(pb_entry.payload, payload)
         self.assertIs(pb_entry.payload_pb, payload)
-        self.assertIsNone(pb_entry.payload)
+        self.assertIsNone(pb_entry.payload_json)
         self.assertIs(pb_entry.logger, mock.sentinel.logger)
         self.assertIsNone(pb_entry.insert_id)
         self.assertIsNone(pb_entry.timestamp)
@@ -277,20 +724,125 @@ def test_constructor_with_any(self):
         self.assertIsNone(pb_entry.http_request)
         self.assertIsNone(pb_entry.trace)
         self.assertIsNone(pb_entry.span_id)
+        self.assertIsNone(pb_entry.trace_sampled)
+        self.assertIsNone(pb_entry.source_location)
 
     def test_parse_message(self):
         import json
         from google.protobuf.json_format import MessageToJson
         from google.protobuf.struct_pb2 import Struct, Value
 
-        LOGGER = object()
         message = Struct(fields={'foo': Value(bool_value=False)})
         with_true = Struct(fields={'foo': Value(bool_value=True)})
-        PAYLOAD = json.loads(MessageToJson(with_true))
-        entry = self._make_one(PAYLOAD, LOGGER)
+        payload = json.loads(MessageToJson(with_true))
+        entry = self._make_one(payload=payload, logger=mock.sentinel.logger)
+
         entry.parse_message(message)
+
         self.assertTrue(message.fields['foo'])
 
+    def test_to_api_repr_proto_defaults(self):
+        from google.protobuf.json_format import MessageToDict
+        from google.cloud.logging.logger import _GLOBAL_RESOURCE
+        from google.protobuf.struct_pb2 import Struct
+        from google.protobuf.struct_pb2 import Value
+
+        LOG_NAME = 'test.log'
+        message = Struct(fields={'foo': Value(bool_value=True)})
+
+        entry = self._make_one(log_name=LOG_NAME, payload=message)
+        expected = {
+            'logName': LOG_NAME,
+            'protoPayload': MessageToDict(message),
+            'resource': _GLOBAL_RESOURCE._to_dict(),
+        }
+        self.assertEqual(entry.to_api_repr(), expected)
+
+    def test_to_api_repr_proto_explicit(self):
+        import datetime
+        from google.protobuf.json_format import MessageToDict
+        from google.cloud.logging.resource import Resource
+        from google.cloud._helpers import _datetime_to_rfc3339
+        from google.protobuf.struct_pb2 import Struct
+        from google.protobuf.struct_pb2 import Value
+
+        LOG_NAME = 'test.log'
+        message = Struct(fields={'foo': Value(bool_value=True)})
+        LABELS = {'foo': 'bar', 'baz': 'qux'}
+        IID = 'IID'
+        SEVERITY = 'CRITICAL'
+        METHOD = 'POST'
+        URI = 'https://api.example.com/endpoint'
+        STATUS = '500'
+        REQUEST = {
+            'requestMethod': METHOD,
+            'requestUrl': URI,
+            'status': STATUS,
+        }
+        TIMESTAMP = datetime.datetime(2016, 12, 31, 0, 1, 2, 999999)
+        RESOURCE = Resource(
+            type='gae_app',
+            labels={
+                'module_id': 'default',
+                'version_id': 'test'
+            }
+        )
+        TRACE = '12345678-1234-5678-1234-567812345678'
+        SPANID = '000000000000004a'
+        FILE = 'my_file.py'
+        LINE = 123
+        FUNCTION = 'my_function'
+        SOURCE_LOCATION = {
+            'file': FILE,
+            'line': LINE,
+            'function': FUNCTION,
+        }
+        OP_ID = 'OP_ID'
+        PRODUCER = 'PRODUCER'
+        OPERATION = {
+            'id': OP_ID,
+            'producer': PRODUCER,
+            'first': True,
+            'last': False,
+        }
+        expected = {
+            'logName': LOG_NAME,
+            'protoPayload': MessageToDict(message),
+            'labels': LABELS,
+            'insertId': IID,
+            'severity': SEVERITY,
+            'httpRequest': REQUEST,
+            'timestamp': _datetime_to_rfc3339(TIMESTAMP),
+            'resource': RESOURCE._to_dict(),
+            'trace': TRACE,
+            'spanId': SPANID,
+            'traceSampled': True,
+            'sourceLocation': {
+                'file': FILE,
+                'line': str(LINE),
+                'function': FUNCTION,
+            },
+            'operation': OPERATION,
+        }
+
+        entry = self._make_one(
+            log_name=LOG_NAME,
+            payload=message,
+            labels=LABELS,
+            insert_id=IID,
+            severity=SEVERITY,
+            http_request=REQUEST,
+            timestamp=TIMESTAMP,
+            resource=RESOURCE,
+            trace=TRACE,
+            span_id=SPANID,
+            trace_sampled=True,
+            source_location=SOURCE_LOCATION,
+            operation=OPERATION,
+        )
+
+        self.assertEqual(entry.to_api_repr(), expected)
+
 
 def _datetime_to_rfc3339_w_nanos(value):
     from google.cloud._helpers import _RFC3339_NO_FRACTION
diff --git a/logging/tests/unit/test_logger.py b/logging/tests/unit/test_logger.py
--- a/logging/tests/unit/test_logger.py
+++ b/logging/tests/unit/test_logger.py
@@ -88,7 +88,7 @@ def test_batch_w_alternate_client(self):
         self.assertIs(batch.logger, logger)
         self.assertIs(batch.client, client2)
 
-    def test_log_empty_w_default_labels(self):
+    def test_log_empty_defaults_w_default_labels(self):
         DEFAULT_LABELS = {'foo': 'spam'}
         ENTRIES = [{
             'logName': 'projects/%s/logs/%s' % (
@@ -109,7 +109,11 @@ def test_log_empty_w_default_labels(self):
         self.assertEqual(api._write_entries_called_with,
                          (ENTRIES, None, None, None))
 
-    def test_log_empty_w_explicit_client_labels_severity_httpreq(self):
+    def test_log_empty_w_explicit(self):
+        import datetime
+        from google.cloud.logging.resource import Resource
+
+        ALT_LOG_NAME = 'projects/foo/logs/alt.log.name'
         DEFAULT_LABELS = {'foo': 'spam'}
         LABELS = {'foo': 'bar', 'baz': 'qux'}
         IID = 'IID'
@@ -124,19 +128,25 @@ def test_log_empty_w_explicit_client_labels_severity_httpreq(self):
             'requestUrl': URI,
             'status': STATUS,
         }
+        TIMESTAMP = datetime.datetime(2016, 12, 31, 0, 1, 2, 999999)
+        RESOURCE = Resource(
+            type='gae_app',
+            labels={
+                'module_id': 'default',
+                'version_id': 'test'
+            }
+        )
         ENTRIES = [{
-            'logName': 'projects/%s/logs/%s' % (
-                self.PROJECT, self.LOGGER_NAME),
-            'resource': {
-                'type': 'global',
-                'labels': {},
-            },
+            'logName': ALT_LOG_NAME,
             'labels': LABELS,
             'insertId': IID,
             'severity': SEVERITY,
             'httpRequest': REQUEST,
+            'timestamp': '2016-12-31T00:01:02.999999Z',
+            'resource': RESOURCE._to_dict(),
             'trace': TRACE,
-            'spanId': SPANID
+            'spanId': SPANID,
+            'traceSampled': True,
         }]
         client1 = _Client(self.PROJECT)
         client2 = _Client(self.PROJECT)
@@ -144,14 +154,24 @@ def test_log_empty_w_explicit_client_labels_severity_httpreq(self):
         logger = self._make_one(self.LOGGER_NAME, client=client1,
                                 labels=DEFAULT_LABELS)
 
-        logger.log_empty(client=client2, labels=LABELS,
-                         insert_id=IID, severity=SEVERITY,
-                         http_request=REQUEST, trace=TRACE, span_id=SPANID)
+        logger.log_empty(
+            log_name=ALT_LOG_NAME,
+            client=client2,
+            labels=LABELS,
+            insert_id=IID,
+            severity=SEVERITY,
+            http_request=REQUEST,
+            timestamp=TIMESTAMP,
+            resource=RESOURCE,
+            trace=TRACE,
+            span_id=SPANID,
+            trace_sampled=True,
+        )
 
         self.assertEqual(api._write_entries_called_with,
                          (ENTRIES, None, None, None))
 
-    def test_log_text_w_str_implicit_client(self):
+    def test_log_text_defaults(self):
         TEXT = 'TEXT'
         ENTRIES = [{
             'logName': 'projects/%s/logs/%s' % (
@@ -171,8 +191,8 @@ def test_log_text_w_str_implicit_client(self):
         self.assertEqual(api._write_entries_called_with,
                          (ENTRIES, None, None, None))
 
-    def test_log_text_w_default_labels(self):
-        TEXT = 'TEXT'
+    def test_log_text_w_unicode_and_default_labels(self):
+        TEXT = u'TEXT'
         DEFAULT_LABELS = {'foo': 'spam'}
         ENTRIES = [{
             'logName': 'projects/%s/logs/%s' % (
@@ -194,78 +214,12 @@ def test_log_text_w_default_labels(self):
         self.assertEqual(api._write_entries_called_with,
                          (ENTRIES, None, None, None))
 
-    def test_log_text_w_timestamp(self):
+    def test_log_text_explicit(self):
         import datetime
+        from google.cloud.logging.resource import Resource
 
+        ALT_LOG_NAME = 'projects/foo/logs/alt.log.name'
         TEXT = 'TEXT'
-        TIMESTAMP = datetime.datetime(2016, 12, 31, 0, 1, 2, 999999)
-        ENTRIES = [{
-            'logName': 'projects/%s/logs/%s' % (
-                self.PROJECT, self.LOGGER_NAME),
-            'textPayload': TEXT,
-            'timestamp': '2016-12-31T00:01:02.999999Z',
-            'resource': {
-                'type': 'global',
-                'labels': {},
-            },
-        }]
-        client = _Client(self.PROJECT)
-        api = client.logging_api = _DummyLoggingAPI()
-        logger = self._make_one(self.LOGGER_NAME, client=client)
-
-        logger.log_text(TEXT, timestamp=TIMESTAMP)
-
-        self.assertEqual(api._write_entries_called_with,
-                         (ENTRIES, None, None, None))
-
-    def test_log_text_w_trace(self):
-
-        TEXT = 'TEXT'
-        TRACE = '12345678-1234-5678-1234-567812345678'
-        ENTRIES = [{
-            'logName': 'projects/%s/logs/%s' % (
-                self.PROJECT, self.LOGGER_NAME),
-            'textPayload': TEXT,
-            'resource': {
-                'type': 'global',
-                'labels': {},
-            },
-            'trace': TRACE
-        }]
-        client = _Client(self.PROJECT)
-        api = client.logging_api = _DummyLoggingAPI()
-        logger = self._make_one(self.LOGGER_NAME, client=client)
-
-        logger.log_text(TEXT, trace=TRACE)
-
-        self.assertEqual(api._write_entries_called_with,
-                         (ENTRIES, None, None, None))
-
-    def test_log_text_w_span(self):
-
-        TEXT = 'TEXT'
-        SPANID = '000000000000004a'
-        ENTRIES = [{
-            'logName': 'projects/%s/logs/%s' % (
-                self.PROJECT, self.LOGGER_NAME),
-            'textPayload': TEXT,
-            'resource': {
-                'type': 'global',
-                'labels': {},
-            },
-            'spanId': SPANID
-        }]
-        client = _Client(self.PROJECT)
-        api = client.logging_api = _DummyLoggingAPI()
-        logger = self._make_one(self.LOGGER_NAME, client=client)
-
-        logger.log_text(TEXT, span_id=SPANID)
-
-        self.assertEqual(api._write_entries_called_with,
-                         (ENTRIES, None, None, None))
-
-    def test_log_text_w_unicode_explicit_client_labels_severity_httpreq(self):
-        TEXT = u'TEXT'
         DEFAULT_LABELS = {'foo': 'spam'}
         LABELS = {'foo': 'bar', 'baz': 'qux'}
         IID = 'IID'
@@ -280,35 +234,52 @@ def test_log_text_w_unicode_explicit_client_labels_severity_httpreq(self):
             'requestUrl': URI,
             'status': STATUS,
         }
+        TIMESTAMP = datetime.datetime(2016, 12, 31, 0, 1, 2, 999999)
+        RESOURCE = Resource(
+            type='gae_app',
+            labels={
+                'module_id': 'default',
+                'version_id': 'test'
+            }
+        )
         ENTRIES = [{
-            'logName': 'projects/%s/logs/%s' % (
-                self.PROJECT, self.LOGGER_NAME),
+            'logName': ALT_LOG_NAME,
             'textPayload': TEXT,
-            'resource': {
-                'type': 'global',
-                'labels': {},
-            },
             'labels': LABELS,
             'insertId': IID,
             'severity': SEVERITY,
             'httpRequest': REQUEST,
+            'timestamp': '2016-12-31T00:01:02.999999Z',
+            'resource': RESOURCE._to_dict(),
             'trace': TRACE,
-            'spanId': SPANID
+            'spanId': SPANID,
+            'traceSampled': True,
         }]
         client1 = _Client(self.PROJECT)
         client2 = _Client(self.PROJECT)
         api = client2.logging_api = _DummyLoggingAPI()
-        logger = self._make_one(self.LOGGER_NAME, client=client1,
-                                labels=DEFAULT_LABELS)
-
-        logger.log_text(TEXT, client=client2, labels=LABELS,
-                        insert_id=IID, severity=SEVERITY, http_request=REQUEST,
-                        trace=TRACE, span_id=SPANID)
+        logger = self._make_one(
+            self.LOGGER_NAME, client=client1, labels=DEFAULT_LABELS)
+
+        logger.log_text(
+            TEXT,
+            log_name=ALT_LOG_NAME,
+            client=client2,
+            labels=LABELS,
+            insert_id=IID,
+            severity=SEVERITY,
+            http_request=REQUEST,
+            timestamp=TIMESTAMP,
+            resource=RESOURCE,
+            trace=TRACE,
+            span_id=SPANID,
+            trace_sampled=True,
+        )
 
         self.assertEqual(api._write_entries_called_with,
                          (ENTRIES, None, None, None))
 
-    def test_log_struct_w_implicit_client(self):
+    def test_log_struct_defaults(self):
         STRUCT = {'message': 'MESSAGE', 'weather': 'cloudy'}
         ENTRIES = [{
             'logName': 'projects/%s/logs/%s' % (
@@ -351,7 +322,11 @@ def test_log_struct_w_default_labels(self):
         self.assertEqual(api._write_entries_called_with,
                          (ENTRIES, None, None, None))
 
-    def test_log_struct_w_explicit_client_labels_severity_httpreq(self):
+    def test_log_struct_w_explicit(self):
+        import datetime
+        from google.cloud.logging.resource import Resource
+
+        ALT_LOG_NAME = 'projects/foo/logs/alt.log.name'
         STRUCT = {'message': 'MESSAGE', 'weather': 'cloudy'}
         DEFAULT_LABELS = {'foo': 'spam'}
         LABELS = {'foo': 'bar', 'baz': 'qux'}
@@ -367,20 +342,26 @@ def test_log_struct_w_explicit_client_labels_severity_httpreq(self):
             'requestUrl': URI,
             'status': STATUS,
         }
+        TIMESTAMP = datetime.datetime(2016, 12, 31, 0, 1, 2, 999999)
+        RESOURCE = Resource(
+            type='gae_app',
+            labels={
+                'module_id': 'default',
+                'version_id': 'test'
+            }
+        )
         ENTRIES = [{
-            'logName': 'projects/%s/logs/%s' % (
-                self.PROJECT, self.LOGGER_NAME),
+            'logName': ALT_LOG_NAME,
             'jsonPayload': STRUCT,
-            'resource': {
-                'type': 'global',
-                'labels': {},
-            },
             'labels': LABELS,
             'insertId': IID,
             'severity': SEVERITY,
             'httpRequest': REQUEST,
+            'timestamp': '2016-12-31T00:01:02.999999Z',
+            'resource': RESOURCE._to_dict(),
             'trace': TRACE,
-            'spanId': SPANID
+            'spanId': SPANID,
+            'traceSampled': True,
         }]
         client1 = _Client(self.PROJECT)
         client2 = _Client(self.PROJECT)
@@ -388,84 +369,25 @@ def test_log_struct_w_explicit_client_labels_severity_httpreq(self):
         logger = self._make_one(self.LOGGER_NAME, client=client1,
                                 labels=DEFAULT_LABELS)
 
-        logger.log_struct(STRUCT, client=client2, labels=LABELS,
-                          insert_id=IID, severity=SEVERITY,
-                          http_request=REQUEST, trace=TRACE, span_id=SPANID)
-
-        self.assertEqual(api._write_entries_called_with,
-                         (ENTRIES, None, None, None))
-
-    def test_log_struct_w_timestamp(self):
-        import datetime
-
-        STRUCT = {'message': 'MESSAGE', 'weather': 'cloudy'}
-        TIMESTAMP = datetime.datetime(2016, 12, 31, 0, 1, 2, 999999)
-        ENTRIES = [{
-            'logName': 'projects/%s/logs/%s' % (
-                self.PROJECT, self.LOGGER_NAME),
-            'jsonPayload': STRUCT,
-            'timestamp': '2016-12-31T00:01:02.999999Z',
-            'resource': {
-                'type': 'global',
-                'labels': {},
-            },
-        }]
-        client = _Client(self.PROJECT)
-        api = client.logging_api = _DummyLoggingAPI()
-        logger = self._make_one(self.LOGGER_NAME, client=client)
-
-        logger.log_struct(STRUCT, timestamp=TIMESTAMP)
-
-        self.assertEqual(api._write_entries_called_with,
-                         (ENTRIES, None, None, None))
-
-    def test_log_struct_w_trace(self):
-
-        STRUCT = {'message': 'MESSAGE', 'weather': 'cloudy'}
-        TRACE = '12345678-1234-5678-1234-567812345678'
-        ENTRIES = [{
-            'logName': 'projects/%s/logs/%s' % (
-                self.PROJECT, self.LOGGER_NAME),
-            'jsonPayload': STRUCT,
-            'resource': {
-                'type': 'global',
-                'labels': {},
-            },
-            'trace': TRACE
-        }]
-        client = _Client(self.PROJECT)
-        api = client.logging_api = _DummyLoggingAPI()
-        logger = self._make_one(self.LOGGER_NAME, client=client)
-
-        logger.log_struct(STRUCT, trace=TRACE)
-
-        self.assertEqual(api._write_entries_called_with,
-                         (ENTRIES, None, None, None))
-
-    def test_log_struct_w_span(self):
-
-        STRUCT = {'message': 'MESSAGE', 'weather': 'cloudy'}
-        SPANID = '000000000000004a'
-        ENTRIES = [{
-            'logName': 'projects/%s/logs/%s' % (
-                self.PROJECT, self.LOGGER_NAME),
-            'jsonPayload': STRUCT,
-            'resource': {
-                'type': 'global',
-                'labels': {},
-            },
-            'spanId': SPANID
-        }]
-        client = _Client(self.PROJECT)
-        api = client.logging_api = _DummyLoggingAPI()
-        logger = self._make_one(self.LOGGER_NAME, client=client)
-
-        logger.log_struct(STRUCT, span_id=SPANID)
+        logger.log_struct(
+            STRUCT,
+            log_name=ALT_LOG_NAME,
+            client=client2,
+            labels=LABELS,
+            insert_id=IID,
+            severity=SEVERITY,
+            http_request=REQUEST,
+            timestamp=TIMESTAMP,
+            resource=RESOURCE,
+            trace=TRACE,
+            span_id=SPANID,
+            trace_sampled=True,
+        )
 
         self.assertEqual(api._write_entries_called_with,
                          (ENTRIES, None, None, None))
 
-    def test_log_proto_w_implicit_client(self):
+    def test_log_proto_defaults(self):
         import json
         from google.protobuf.json_format import MessageToJson
         from google.protobuf.struct_pb2 import Struct, Value
@@ -516,13 +438,16 @@ def test_log_proto_w_default_labels(self):
         self.assertEqual(api._write_entries_called_with,
                          (ENTRIES, None, None, None))
 
-    def test_log_proto_w_explicit_client_labels_severity_httpreq(self):
+    def test_log_proto_w_explicit(self):
         import json
+        import datetime
         from google.protobuf.json_format import MessageToJson
         from google.protobuf.struct_pb2 import Struct
         from google.protobuf.struct_pb2 import Value
+        from google.cloud.logging.resource import Resource
 
         message = Struct(fields={'foo': Value(bool_value=True)})
+        ALT_LOG_NAME = 'projects/foo/logs/alt.log.name'
         DEFAULT_LABELS = {'foo': 'spam'}
         LABELS = {'foo': 'bar', 'baz': 'qux'}
         IID = 'IID'
@@ -537,20 +462,26 @@ def test_log_proto_w_explicit_client_labels_severity_httpreq(self):
             'requestUrl': URI,
             'status': STATUS,
         }
+        TIMESTAMP = datetime.datetime(2016, 12, 31, 0, 1, 2, 999999)
+        RESOURCE = Resource(
+            type='gae_app',
+            labels={
+                'module_id': 'default',
+                'version_id': 'test'
+            }
+        )
         ENTRIES = [{
-            'logName': 'projects/%s/logs/%s' % (
-                self.PROJECT, self.LOGGER_NAME),
+            'logName': ALT_LOG_NAME,
             'protoPayload': json.loads(MessageToJson(message)),
-            'resource': {
-                'type': 'global',
-                'labels': {},
-            },
             'labels': LABELS,
             'insertId': IID,
             'severity': SEVERITY,
             'httpRequest': REQUEST,
+            'timestamp': '2016-12-31T00:01:02.999999Z',
+            'resource': RESOURCE._to_dict(),
             'trace': TRACE,
-            'spanId': SPANID
+            'spanId': SPANID,
+            'traceSampled': True,
         }]
         client1 = _Client(self.PROJECT)
         client2 = _Client(self.PROJECT)
@@ -558,91 +489,20 @@ def test_log_proto_w_explicit_client_labels_severity_httpreq(self):
         logger = self._make_one(self.LOGGER_NAME, client=client1,
                                 labels=DEFAULT_LABELS)
 
-        logger.log_proto(message, client=client2, labels=LABELS,
-                         insert_id=IID, severity=SEVERITY,
-                         http_request=REQUEST, trace=TRACE, span_id=SPANID)
-
-        self.assertEqual(api._write_entries_called_with,
-                         (ENTRIES, None, None, None))
-
-    def test_log_proto_w_timestamp(self):
-        import json
-        import datetime
-        from google.protobuf.json_format import MessageToJson
-        from google.protobuf.struct_pb2 import Struct
-        from google.protobuf.struct_pb2 import Value
-
-        message = Struct(fields={'foo': Value(bool_value=True)})
-        TIMESTAMP = datetime.datetime(2016, 12, 31, 0, 1, 2, 999999)
-        ENTRIES = [{
-            'logName': 'projects/%s/logs/%s' % (
-                self.PROJECT, self.LOGGER_NAME),
-            'protoPayload': json.loads(MessageToJson(message)),
-            'timestamp': '2016-12-31T00:01:02.999999Z',
-            'resource': {
-                'type': 'global',
-                'labels': {},
-            },
-        }]
-        client = _Client(self.PROJECT)
-        api = client.logging_api = _DummyLoggingAPI()
-        logger = self._make_one(self.LOGGER_NAME, client=client)
-
-        logger.log_proto(message, timestamp=TIMESTAMP)
-
-        self.assertEqual(api._write_entries_called_with,
-                         (ENTRIES, None, None, None))
-
-    def test_log_proto_w_trace(self):
-        import json
-        from google.protobuf.json_format import MessageToJson
-        from google.protobuf.struct_pb2 import Struct
-        from google.protobuf.struct_pb2 import Value
-
-        message = Struct(fields={'foo': Value(bool_value=True)})
-        TRACE = '12345678-1234-5678-1234-567812345678'
-        ENTRIES = [{
-            'logName': 'projects/%s/logs/%s' % (
-                self.PROJECT, self.LOGGER_NAME),
-            'protoPayload': json.loads(MessageToJson(message)),
-            'resource': {
-                'type': 'global',
-                'labels': {},
-            },
-            'trace': TRACE
-        }]
-        client = _Client(self.PROJECT)
-        api = client.logging_api = _DummyLoggingAPI()
-        logger = self._make_one(self.LOGGER_NAME, client=client)
-
-        logger.log_proto(message, trace=TRACE)
-
-        self.assertEqual(api._write_entries_called_with,
-                         (ENTRIES, None, None, None))
-
-    def test_log_proto_w_span(self):
-        import json
-        from google.protobuf.json_format import MessageToJson
-        from google.protobuf.struct_pb2 import Struct
-        from google.protobuf.struct_pb2 import Value
-
-        message = Struct(fields={'foo': Value(bool_value=True)})
-        SPANID = '000000000000004a'
-        ENTRIES = [{
-            'logName': 'projects/%s/logs/%s' % (
-                self.PROJECT, self.LOGGER_NAME),
-            'protoPayload': json.loads(MessageToJson(message)),
-            'resource': {
-                'type': 'global',
-                'labels': {},
-            },
-            'spanId': SPANID
-        }]
-        client = _Client(self.PROJECT)
-        api = client.logging_api = _DummyLoggingAPI()
-        logger = self._make_one(self.LOGGER_NAME, client=client)
-
-        logger.log_proto(message, span_id=SPANID)
+        logger.log_proto(
+            message,
+            log_name=ALT_LOG_NAME,
+            client=client2,
+            labels=LABELS,
+            insert_id=IID,
+            severity=SEVERITY,
+            http_request=REQUEST,
+            timestamp=TIMESTAMP,
+            resource=RESOURCE,
+            trace=TRACE,
+            span_id=SPANID,
+            trace_sampled=True,
+        )
 
         self.assertEqual(api._write_entries_called_with,
                          (ENTRIES, None, None, None))
@@ -763,20 +623,86 @@ def test_ctor_defaults(self):
         self.assertIs(batch.client, client)
         self.assertEqual(len(batch.entries), 0)
 
+    def test_log_empty_defaults(self):
+        from google.cloud.logging.entries import LogEntry
+
+        ENTRY = LogEntry()
+        client = _Client(project=self.PROJECT, connection=_make_credentials())
+        logger = _Logger()
+        batch = self._make_one(logger, client=client)
+        batch.log_empty()
+        self.assertEqual(batch.entries, [ENTRY])
+
+    def test_log_empty_explicit(self):
+        import datetime
+        from google.cloud.logging.resource import Resource
+        from google.cloud.logging.entries import LogEntry
+
+        LABELS = {'foo': 'bar', 'baz': 'qux'}
+        IID = 'IID'
+        SEVERITY = 'CRITICAL'
+        METHOD = 'POST'
+        URI = 'https://api.example.com/endpoint'
+        STATUS = '500'
+        REQUEST = {
+            'requestMethod': METHOD,
+            'requestUrl': URI,
+            'status': STATUS,
+        }
+        TIMESTAMP = datetime.datetime(2016, 12, 31, 0, 1, 2, 999999)
+        RESOURCE = Resource(
+            type='gae_app',
+            labels={
+                'module_id': 'default',
+                'version_id': 'test'
+            }
+        )
+        TRACE = '12345678-1234-5678-1234-567812345678'
+        SPANID = '000000000000004a'
+        ENTRY = LogEntry(
+            labels=LABELS,
+            insert_id=IID,
+            severity=SEVERITY,
+            http_request=REQUEST,
+            timestamp=TIMESTAMP,
+            resource=RESOURCE,
+            trace=TRACE,
+            span_id=SPANID,
+            trace_sampled=True,
+        )
+
+        client = _Client(project=self.PROJECT, connection=_make_credentials())
+        logger = _Logger()
+        batch = self._make_one(logger, client=client)
+        batch.log_empty(
+            labels=LABELS,
+            insert_id=IID,
+            severity=SEVERITY,
+            http_request=REQUEST,
+            timestamp=TIMESTAMP,
+            resource=RESOURCE,
+            trace=TRACE,
+            span_id=SPANID,
+            trace_sampled=True,
+        )
+        self.assertEqual(batch.entries, [ENTRY])
+
     def test_log_text_defaults(self):
-        from google.cloud.logging.logger import _GLOBAL_RESOURCE
+        from google.cloud.logging.entries import _GLOBAL_RESOURCE
+        from google.cloud.logging.entries import TextEntry
+
         TEXT = 'This is the entry text'
+        ENTRY = TextEntry(payload=TEXT, resource=_GLOBAL_RESOURCE)
         client = _Client(project=self.PROJECT, connection=_make_credentials())
         logger = _Logger()
         batch = self._make_one(logger, client=client)
         batch.log_text(TEXT)
-        self.assertEqual(batch.entries,
-                         [('text', TEXT, None, None, None, None, None,
-                           _GLOBAL_RESOURCE, None, None)])
+        self.assertEqual(batch.entries, [ENTRY])
 
     def test_log_text_explicit(self):
         import datetime
         from google.cloud.logging.resource import Resource
+        from google.cloud.logging.entries import TextEntry
 
         TEXT = 'This is the entry text'
         LABELS = {'foo': 'bar', 'baz': 'qux'}
@@ -800,33 +726,52 @@ def test_log_text_explicit(self):
         )
         TRACE = '12345678-1234-5678-1234-567812345678'
         SPANID = '000000000000004a'
+        ENTRY = TextEntry(
+            payload=TEXT,
+            labels=LABELS,
+            insert_id=IID,
+            severity=SEVERITY,
+            http_request=REQUEST,
+            timestamp=TIMESTAMP,
+            resource=RESOURCE,
+            trace=TRACE,
+            span_id=SPANID,
+            trace_sampled=True,
+        )
 
         client = _Client(project=self.PROJECT, connection=_make_credentials())
         logger = _Logger()
         batch = self._make_one(logger, client=client)
-        batch.log_text(TEXT, labels=LABELS, insert_id=IID, severity=SEVERITY,
-                       http_request=REQUEST, timestamp=TIMESTAMP,
-                       resource=RESOURCE, trace=TRACE, span_id=SPANID)
-        self.assertEqual(
-            batch.entries,
-            [('text', TEXT, LABELS, IID, SEVERITY, REQUEST, TIMESTAMP,
-              RESOURCE, TRACE, SPANID)])
+        batch.log_text(
+            TEXT,
+            labels=LABELS,
+            insert_id=IID,
+            severity=SEVERITY,
+            http_request=REQUEST,
+            timestamp=TIMESTAMP,
+            resource=RESOURCE,
+            trace=TRACE,
+            span_id=SPANID,
+            trace_sampled=True,
+        )
+        self.assertEqual(batch.entries, [ENTRY])
 
     def test_log_struct_defaults(self):
-        from google.cloud.logging.logger import _GLOBAL_RESOURCE
+        from google.cloud.logging.entries import _GLOBAL_RESOURCE
+        from google.cloud.logging.entries import StructEntry
+
         STRUCT = {'message': 'Message text', 'weather': 'partly cloudy'}
+        ENTRY = StructEntry(payload=STRUCT, resource=_GLOBAL_RESOURCE)
         client = _Client(project=self.PROJECT, connection=_make_credentials())
         logger = _Logger()
         batch = self._make_one(logger, client=client)
         batch.log_struct(STRUCT)
-        self.assertEqual(
-            batch.entries,
-            [('struct', STRUCT, None, None, None, None, None,
-              _GLOBAL_RESOURCE, None, None)])
+        self.assertEqual(batch.entries, [ENTRY])
 
     def test_log_struct_explicit(self):
         import datetime
         from google.cloud.logging.resource import Resource
+        from google.cloud.logging.entries import StructEntry
 
         STRUCT = {'message': 'Message text', 'weather': 'partly cloudy'}
         LABELS = {'foo': 'bar', 'baz': 'qux'}
@@ -850,36 +795,54 @@ def test_log_struct_explicit(self):
                 'version_id': 'test',
             }
         )
+        ENTRY = StructEntry(
+            payload=STRUCT,
+            labels=LABELS,
+            insert_id=IID,
+            severity=SEVERITY,
+            http_request=REQUEST,
+            timestamp=TIMESTAMP,
+            resource=RESOURCE,
+            trace=TRACE,
+            span_id=SPANID,
+            trace_sampled=True,
+        )
 
         client = _Client(project=self.PROJECT, connection=_make_credentials())
         logger = _Logger()
         batch = self._make_one(logger, client=client)
-        batch.log_struct(STRUCT, labels=LABELS, insert_id=IID,
-                         severity=SEVERITY, http_request=REQUEST,
-                         timestamp=TIMESTAMP, resource=RESOURCE, trace=TRACE,
-                         span_id=SPANID)
-        self.assertEqual(
-            batch.entries,
-            [('struct', STRUCT, LABELS, IID, SEVERITY, REQUEST, TIMESTAMP,
-              RESOURCE, TRACE, SPANID)])
+        batch.log_struct(
+            STRUCT,
+            labels=LABELS,
+            insert_id=IID,
+            severity=SEVERITY,
+            http_request=REQUEST,
+            timestamp=TIMESTAMP,
+            resource=RESOURCE,
+            trace=TRACE,
+            span_id=SPANID,
+            trace_sampled=True,
+        )
+        self.assertEqual(batch.entries, [ENTRY])
 
     def test_log_proto_defaults(self):
-        from google.cloud.logging.logger import _GLOBAL_RESOURCE
+        from google.cloud.logging.entries import _GLOBAL_RESOURCE
+        from google.cloud.logging.entries import ProtobufEntry
         from google.protobuf.struct_pb2 import Struct
         from google.protobuf.struct_pb2 import Value
 
         message = Struct(fields={'foo': Value(bool_value=True)})
+        ENTRY = ProtobufEntry(payload=message, resource=_GLOBAL_RESOURCE)
         client = _Client(project=self.PROJECT, connection=_make_credentials())
         logger = _Logger()
         batch = self._make_one(logger, client=client)
         batch.log_proto(message)
-        self.assertEqual(batch.entries,
-                         [('proto', message, None, None, None, None, None,
-                           _GLOBAL_RESOURCE, None, None)])
+        self.assertEqual(batch.entries, [ENTRY])
 
     def test_log_proto_explicit(self):
         import datetime
         from google.cloud.logging.resource import Resource
+        from google.cloud.logging.entries import ProtobufEntry
         from google.protobuf.struct_pb2 import Struct
         from google.protobuf.struct_pb2 import Value
 
@@ -905,29 +868,57 @@ def test_log_proto_explicit(self):
                 'version_id': 'test',
             }
         )
+        ENTRY = ProtobufEntry(
+            payload=message,
+            labels=LABELS,
+            insert_id=IID,
+            severity=SEVERITY,
+            http_request=REQUEST,
+            timestamp=TIMESTAMP,
+            resource=RESOURCE,
+            trace=TRACE,
+            span_id=SPANID,
+            trace_sampled=True,
+        )
         client = _Client(project=self.PROJECT, connection=_make_credentials())
         logger = _Logger()
         batch = self._make_one(logger, client=client)
-        batch.log_proto(message, labels=LABELS, insert_id=IID,
-                        severity=SEVERITY, http_request=REQUEST,
-                        timestamp=TIMESTAMP, resource=RESOURCE, trace=TRACE,
-                        span_id=SPANID)
-        self.assertEqual(
-            batch.entries,
-            [('proto', message, LABELS, IID, SEVERITY, REQUEST, TIMESTAMP,
-              RESOURCE, TRACE, SPANID)])
-
-    def test_commit_w_invalid_entry_type(self):
+        batch.log_proto(
+            message,
+            labels=LABELS,
+            insert_id=IID,
+            severity=SEVERITY,
+            http_request=REQUEST,
+            timestamp=TIMESTAMP,
+            resource=RESOURCE,
+            trace=TRACE,
+            span_id=SPANID,
+            trace_sampled=True,
+        )
+        self.assertEqual(batch.entries, [ENTRY])
+
+    def test_commit_w_unknown_entry_type(self):
+        from google.cloud.logging.entries import _GLOBAL_RESOURCE
+        from google.cloud.logging.entries import LogEntry
+
         logger = _Logger()
         client = _Client(project=self.PROJECT, connection=_make_credentials())
+        api = client.logging_api = _DummyLoggingAPI()
         batch = self._make_one(logger, client)
-        batch.entries.append(('bogus', 'BOGUS', None, None, None, None, None,
-                              None, None, None))
-        with self.assertRaises(ValueError):
-            batch.commit()
+        batch.entries.append(LogEntry(severity='blah'))
+        ENTRY = {
+            'severity': 'blah',
+            'resource': _GLOBAL_RESOURCE._to_dict(),
+        }
+
+        batch.commit()
+
+        self.assertEqual(list(batch.entries), [])
+        self.assertEqual(api._write_entries_called_with,
+                         ([ENTRY], logger.full_name, None, None))
 
     def test_commit_w_resource_specified(self):
-        from google.cloud.logging.logger import _GLOBAL_RESOURCE
+        from google.cloud.logging.entries import _GLOBAL_RESOURCE
         from google.cloud.logging.resource import Resource
 
         logger = _Logger()
@@ -961,7 +952,7 @@ def test_commit_w_bound_client(self):
         from google.protobuf.struct_pb2 import Struct
         from google.protobuf.struct_pb2 import Value
         from google.cloud._helpers import _datetime_to_rfc3339
-        from google.cloud.logging.logger import _GLOBAL_RESOURCE
+        from google.cloud.logging.entries import _GLOBAL_RESOURCE
 
         TEXT = 'This is the entry text'
         STRUCT = {'message': TEXT, 'weather': 'partly cloudy'}
@@ -978,32 +969,60 @@ def test_commit_w_bound_client(self):
         SPANID1 = '000000000000004a'
         SPANID2 = '000000000000004b'
         SPANID3 = '000000000000004c'
-        ENTRIES = [
-            {'textPayload': TEXT, 'insertId': IID1,
-             'timestamp': _datetime_to_rfc3339(TIMESTAMP1),
-             'resource': _GLOBAL_RESOURCE._to_dict(), 'trace': TRACE1,
-             'spanId': SPANID1},
-            {'jsonPayload': STRUCT, 'insertId': IID2,
-             'timestamp': _datetime_to_rfc3339(TIMESTAMP2),
-             'resource': _GLOBAL_RESOURCE._to_dict(), 'trace': TRACE2,
-             'spanId': SPANID2},
-            {'protoPayload': json.loads(MessageToJson(message)),
-             'insertId': IID3,
-             'timestamp': _datetime_to_rfc3339(TIMESTAMP3),
-             'resource': _GLOBAL_RESOURCE._to_dict(), 'trace': TRACE3,
-             'spanId': SPANID3},
-        ]
+        ENTRIES = [{
+            'textPayload': TEXT,
+            'insertId': IID1,
+            'timestamp': _datetime_to_rfc3339(TIMESTAMP1),
+            'resource': _GLOBAL_RESOURCE._to_dict(),
+            'trace': TRACE1,
+            'spanId': SPANID1,
+            'traceSampled': True,
+        }, {
+            'jsonPayload': STRUCT,
+            'insertId': IID2,
+            'timestamp': _datetime_to_rfc3339(TIMESTAMP2),
+            'resource': _GLOBAL_RESOURCE._to_dict(),
+            'trace': TRACE2,
+            'spanId': SPANID2,
+            'traceSampled': False,
+        }, {
+            'protoPayload': json.loads(MessageToJson(message)),
+            'insertId': IID3,
+            'timestamp': _datetime_to_rfc3339(TIMESTAMP3),
+            'resource': _GLOBAL_RESOURCE._to_dict(),
+            'trace': TRACE3,
+            'spanId': SPANID3,
+            'traceSampled': True,
+        }]
         client = _Client(project=self.PROJECT)
         api = client.logging_api = _DummyLoggingAPI()
         logger = _Logger()
         batch = self._make_one(logger, client=client)
 
-        batch.log_text(TEXT, insert_id=IID1, timestamp=TIMESTAMP1,
-                       trace=TRACE1, span_id=SPANID1)
-        batch.log_struct(STRUCT, insert_id=IID2, timestamp=TIMESTAMP2,
-                         trace=TRACE2, span_id=SPANID2)
-        batch.log_proto(message, insert_id=IID3, timestamp=TIMESTAMP3,
-                        trace=TRACE3, span_id=SPANID3)
+        batch.log_text(
+            TEXT,
+            insert_id=IID1,
+            timestamp=TIMESTAMP1,
+            trace=TRACE1,
+            span_id=SPANID1,
+            trace_sampled=True,
+        )
+        batch.log_struct(
+            STRUCT,
+            insert_id=IID2,
+            timestamp=TIMESTAMP2,
+            trace=TRACE2,
+            span_id=SPANID2,
+            trace_sampled=False,
+        )
+        batch.log_proto(
+            message,
+            insert_id=IID3,
+            timestamp=TIMESTAMP3,
+            trace=TRACE3,
+            span_id=SPANID3,
+            trace_sampled=True,
+        )
         batch.commit()
 
         self.assertEqual(list(batch.entries), [])
@@ -1016,7 +1035,7 @@ def test_commit_w_alternate_client(self):
         from google.protobuf.struct_pb2 import Struct
         from google.protobuf.struct_pb2 import Value
         from google.cloud.logging.logger import Logger
-        from google.cloud.logging.logger import _GLOBAL_RESOURCE
+        from google.cloud.logging.entries import _GLOBAL_RESOURCE
 
         TEXT = 'This is the entry text'
         STRUCT = {'message': TEXT, 'weather': 'partly cloudy'}
@@ -1065,7 +1084,7 @@ def test_context_mgr_success(self):
         from google.protobuf.struct_pb2 import Struct
         from google.protobuf.struct_pb2 import Value
         from google.cloud.logging.logger import Logger
-        from google.cloud.logging.logger import _GLOBAL_RESOURCE
+        from google.cloud.logging.entries import _GLOBAL_RESOURCE
 
         TEXT = 'This is the entry text'
         STRUCT = {'message': TEXT, 'weather': 'partly cloudy'}
@@ -1108,7 +1127,9 @@ def test_context_mgr_failure(self):
         import datetime
         from google.protobuf.struct_pb2 import Struct
         from google.protobuf.struct_pb2 import Value
-        from google.cloud.logging.logger import _GLOBAL_RESOURCE
+        from google.cloud.logging.entries import TextEntry
+        from google.cloud.logging.entries import StructEntry
+        from google.cloud.logging.entries import ProtobufEntry
 
         TEXT = 'This is the entry text'
         STRUCT = {'message': TEXT, 'weather': 'partly cloudy'}
@@ -1129,12 +1150,10 @@ def test_context_mgr_failure(self):
         api = client.logging_api = _DummyLoggingAPI()
         logger = _Logger()
         UNSENT = [
-            ('text', TEXT, None, IID, None, None, TIMESTAMP,
-             _GLOBAL_RESOURCE, None, None),
-            ('struct', STRUCT, None, None, SEVERITY, None, None,
-             _GLOBAL_RESOURCE, None, None),
-            ('proto', message, LABELS, None, None, REQUEST, None,
-             _GLOBAL_RESOURCE, None, None),
+            TextEntry(payload=TEXT, insert_id=IID, timestamp=TIMESTAMP),
+            StructEntry(payload=STRUCT, severity=SEVERITY),
+            ProtobufEntry(
+                payload=message, labels=LABELS, http_request=REQUEST),
         ]
         batch = self._make_one(logger, client=client)