Skip to content

Commit 7ae1537

Browse files
authored
fix: Change timestamp type to support nanosecond resolution (#654)
Changes timestamp type to java.time.Instant to support nanosec resolution. Keeps current get/set methods to operate with milliseconds. Updates unit tests to reflect type change and additional API. Fixes #598
1 parent 245df78 commit 7ae1537

File tree

3 files changed

+180
-45
lines changed

3 files changed

+180
-45
lines changed
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright 2021 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.logging;
18+
19+
import static com.google.common.math.LongMath.checkedAdd;
20+
import static com.google.common.math.LongMath.checkedSubtract;
21+
22+
import com.google.protobuf.Timestamp;
23+
import com.google.protobuf.util.Timestamps;
24+
import java.time.Instant;
25+
26+
/**
27+
* A utility class that provides conversions between:
28+
*
29+
* <ul>
30+
* <li>Protobuf's {@link Timestamp} and {@link java.time.Instant}
31+
* </ul>
32+
*
33+
* The class complements convertion methods that are currently not supported in the published
34+
* protobuf-java-util. After migrating protobuf-java-util to Java 8 this class can be removed.
35+
*
36+
* @see <a
37+
* href="https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java-util">protobuf-java-util</a>
38+
*/
39+
class JavaTimeConversions {
40+
static final long NANOS_PER_SECOND = 1000000000;
41+
42+
private JavaTimeConversions() {}
43+
44+
/**
45+
* Converts a protobuf {@link Timestamp} to a {@link java.time.Instant}.
46+
*
47+
* @throws IllegalArgumentException if the given {@link Timestamp} is not valid. See {@link
48+
* Timestamps#isValid}.
49+
*/
50+
public static Instant toJavaInstant(Timestamp timestamp) {
51+
timestamp = normalizedTimestamp(timestamp.getSeconds(), timestamp.getNanos());
52+
return Instant.ofEpochSecond(timestamp.getSeconds(), timestamp.getNanos());
53+
}
54+
55+
/**
56+
* Converts a {@link java.time.Instant} to a protobuf {@link Timestamp}.
57+
*
58+
* @throws IllegalArgumentException if the given {@link java.time.Instant} cannot legally fit into
59+
* a {@link Timestamp}. See {@link Timestamps#isValid}.
60+
*/
61+
public static Timestamp toProtoTimestamp(Instant instant) {
62+
return normalizedTimestamp(instant.getEpochSecond(), instant.getNano());
63+
}
64+
65+
/** Exposes {@link Timestamps#normalizedTimestamp} internal method. */
66+
static Timestamp normalizedTimestamp(long seconds, int nanos) {
67+
if (nanos <= -NANOS_PER_SECOND || nanos >= NANOS_PER_SECOND) {
68+
seconds = checkedAdd(seconds, nanos / NANOS_PER_SECOND);
69+
nanos = (int) (nanos % NANOS_PER_SECOND);
70+
}
71+
if (nanos < 0) {
72+
nanos =
73+
(int)
74+
(nanos + NANOS_PER_SECOND); // no overflow since nanos is negative (and we're adding)
75+
seconds = checkedSubtract(seconds, 1);
76+
}
77+
Timestamp timestamp = Timestamp.newBuilder().setSeconds(seconds).setNanos(nanos).build();
78+
return Timestamps.checkValid(timestamp);
79+
}
80+
}

google-cloud-logging/src/main/java/com/google/cloud/logging/LogEntry.java

Lines changed: 74 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@
2525
import com.google.logging.v2.LogEntryOperation;
2626
import com.google.logging.v2.LogEntrySourceLocation;
2727
import com.google.logging.v2.LogName;
28-
import com.google.protobuf.Timestamp;
2928
import java.io.Serializable;
29+
import java.time.Instant;
3030
import java.util.HashMap;
3131
import java.util.Map;
3232
import java.util.Objects;
@@ -44,8 +44,6 @@
4444
public class LogEntry implements Serializable {
4545

4646
private static final long serialVersionUID = -944788159728228219L;
47-
private static final long NANOS_PER_MILLISECOND = 1000000;
48-
private static final long MILLIS_PER_SECOND = 1000;
4947
static final Function<com.google.logging.v2.LogEntry, LogEntry> FROM_PB_FUNCTION =
5048
new Function<com.google.logging.v2.LogEntry, LogEntry>() {
5149
@Override
@@ -56,8 +54,8 @@ public LogEntry apply(com.google.logging.v2.LogEntry pb) {
5654

5755
private final String logName;
5856
private final MonitoredResource resource;
59-
private final Long timestamp;
60-
private final Long receiveTimestamp;
57+
private final Instant timestamp;
58+
private final Instant receiveTimestamp;
6159
private final Severity severity;
6260
private final String insertId;
6361
private final HttpRequest httpRequest;
@@ -74,8 +72,8 @@ public static class Builder {
7472

7573
private String logName;
7674
private MonitoredResource resource;
77-
private Long timestamp;
78-
private Long receiveTimestamp;
75+
private Instant timestamp;
76+
private Instant receiveTimestamp;
7977
private Severity severity = Severity.DEFAULT;
8078
private String insertId;
8179
private HttpRequest httpRequest;
@@ -133,14 +131,44 @@ public Builder setResource(MonitoredResource resource) {
133131
/**
134132
* Sets the time at which the event described by the log entry occurred, in milliseconds. If
135133
* omitted, the Logging service will use the time at which the log entry is received.
134+
*
135+
* @deprecated This method is no longer recommended to setup the entry timestamp.
136+
* <p>Use {@link setTimeStamp(Instant)} instead.
136137
*/
137-
public Builder setTimestamp(long timestamp) {
138+
@Deprecated
139+
public Builder setTimestamp(long milliseconds) {
140+
this.timestamp = Instant.ofEpochMilli(milliseconds);
141+
return this;
142+
}
143+
144+
/**
145+
* Sets the time at which the event described by the log entry occurred. If omitted, the Logging
146+
* service will use the time at which the log entry is received.
147+
*/
148+
public Builder setTimestamp(Instant timestamp) {
138149
this.timestamp = timestamp;
139150
return this;
140151
}
141152

142-
/** Sets the time the log entry was received by Cloud Logging. */
143-
public Builder setReceiveTimestamp(long receiveTimestamp) {
153+
/**
154+
* Sets the time the log entry was received by Cloud Logging, in milliseconds. If omitted, the
155+
* Logging service will set the time at which the log entry is received.
156+
*
157+
* @deprecated This method is no longer recommended to setup the receive time timestamp.
158+
* <p>Use {@link setReceiveTimestamp(Instant)} instead.
159+
*/
160+
@Deprecated
161+
public Builder setReceiveTimestamp(long milliseconds) {
162+
this.receiveTimestamp = Instant.ofEpochMilli(milliseconds);
163+
;
164+
return this;
165+
}
166+
167+
/**
168+
* Sets the time the log entry was received by Cloud Logging. If omitted, the Logging service
169+
* will set the time at which the log entry is received.
170+
*/
171+
public Builder setReceiveTimestamp(Instant receiveTimestamp) {
144172
this.receiveTimestamp = receiveTimestamp;
145173
return this;
146174
}
@@ -298,15 +326,44 @@ public MonitoredResource getResource() {
298326
}
299327

300328
/**
301-
* Returns the time at which the event described by the log entry occurred, in milliseconds. If
302-
* omitted, the Logging service will use the time at which the log entry is received.
329+
* Returns the time at which the event described by the log entry occurred, in milliseconds.
330+
*
331+
* @deprecated This method is no longer recommended to get the entry timestamp.
332+
* <p>Use {@link getInstantTimestamp()} instead.
333+
* @return timestamp in milliseconds
303334
*/
335+
@Deprecated
304336
public Long getTimestamp() {
337+
return timestamp != null ? timestamp.toEpochMilli() : null;
338+
}
339+
340+
/**
341+
* Returns the time at which the event described by the log entry occurred.
342+
*
343+
* @return timestamp as {@link Instant}
344+
*/
345+
public Instant getInstantTimestamp() {
305346
return timestamp;
306347
}
307348

308-
/** Returns the time the log entry was received by Cloud Logging. */
349+
/**
350+
* Returns the time the log entry was received by Cloud Logging, in milliseconds.
351+
*
352+
* @deprecated This method is no longer recommended to get the received time timestamp.
353+
* <p>Use {@link getInstantReceiveTimestamp()} instead.
354+
* @return timestamp in milliseconds
355+
*/
356+
@Deprecated
309357
public Long getReceiveTimestamp() {
358+
return receiveTimestamp != null ? receiveTimestamp.toEpochMilli() : null;
359+
}
360+
361+
/**
362+
* Returns the time the log entry was received by Cloud Logging, in milliseconds.
363+
*
364+
* @return timestamp as {@link Instant}
365+
*/
366+
public Instant getInstantReceiveTimestamp() {
310367
return receiveTimestamp;
311368
}
312369

@@ -450,18 +507,6 @@ public Builder toBuilder() {
450507
return new Builder(this);
451508
}
452509

453-
private static Timestamp timestampFromMillis(Long millis) {
454-
Timestamp.Builder tsBuilder = Timestamp.newBuilder();
455-
tsBuilder.setSeconds(millis / MILLIS_PER_SECOND);
456-
tsBuilder.setNanos((int) (millis % MILLIS_PER_SECOND * NANOS_PER_MILLISECOND));
457-
return tsBuilder.build();
458-
}
459-
460-
private static Long millisFromTimestamp(Timestamp timestamp) {
461-
return timestamp.getSeconds() * MILLIS_PER_SECOND
462-
+ timestamp.getNanos() / NANOS_PER_MILLISECOND;
463-
}
464-
465510
com.google.logging.v2.LogEntry toPb(String projectId) {
466511
com.google.logging.v2.LogEntry.Builder builder = payload.toPb();
467512
builder.putAllLabels(labels);
@@ -472,10 +517,10 @@ com.google.logging.v2.LogEntry toPb(String projectId) {
472517
builder.setResource(resource.toPb());
473518
}
474519
if (timestamp != null) {
475-
builder.setTimestamp(timestampFromMillis(timestamp));
520+
builder.setTimestamp(JavaTimeConversions.toProtoTimestamp(timestamp));
476521
}
477522
if (receiveTimestamp != null) {
478-
builder.setReceiveTimestamp(timestampFromMillis(receiveTimestamp));
523+
builder.setReceiveTimestamp(JavaTimeConversions.toProtoTimestamp(receiveTimestamp));
479524
}
480525
if (severity != null) {
481526
builder.setSeverity(severity.toPb());
@@ -531,16 +576,10 @@ static LogEntry fromPb(com.google.logging.v2.LogEntry entryPb) {
531576
builder.setResource(MonitoredResource.fromPb(entryPb.getResource()));
532577
}
533578
if (entryPb.hasTimestamp()) {
534-
Long millis = millisFromTimestamp(entryPb.getTimestamp());
535-
if (millis != 0) {
536-
builder.setTimestamp(millis);
537-
}
579+
builder.setTimestamp(JavaTimeConversions.toJavaInstant(entryPb.getTimestamp()));
538580
}
539581
if (entryPb.hasReceiveTimestamp()) {
540-
Long millis = millisFromTimestamp(entryPb.getReceiveTimestamp());
541-
if (millis != 0) {
542-
builder.setReceiveTimestamp(millis);
543-
}
582+
builder.setReceiveTimestamp(JavaTimeConversions.toJavaInstant(entryPb.getReceiveTimestamp()));
544583
}
545584
if (!entryPb.getInsertId().equals("")) {
546585
builder.setInsertId(entryPb.getInsertId());

google-cloud-logging/src/test/java/com/google/cloud/logging/LogEntryTest.java

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import com.google.common.collect.ImmutableMap;
2828
import com.google.protobuf.Any;
2929
import com.google.protobuf.Empty;
30+
import java.time.Instant;
3031
import java.util.Map;
3132
import org.junit.Test;
3233

@@ -37,8 +38,8 @@ public class LogEntryTest {
3738
MonitoredResource.newBuilder("cloudsql_database")
3839
.setLabels(ImmutableMap.of("datasetId", "myDataset", "zone", "myZone"))
3940
.build();
40-
private static final long TIMESTAMP = 42;
41-
private static final long RECEIVE_TIMESTAMP = 24;
41+
private static final Instant TIMESTAMP = Instant.ofEpochMilli(42);
42+
private static final Instant RECEIVE_TIMESTAMP = Instant.ofEpochMilli(24);
4243
private static final Severity SEVERITY = Severity.ALERT;
4344
private static final String INSERT_ID = "insertId";
4445
private static final HttpRequest HTTP_REQUEST =
@@ -131,7 +132,9 @@ public void testOf() {
131132
assertNull(logEntry.getLogName());
132133
assertNull(logEntry.getResource());
133134
assertNull(logEntry.getTimestamp());
135+
assertNull(logEntry.getInstantTimestamp());
134136
assertNull(logEntry.getReceiveTimestamp());
137+
assertNull(logEntry.getInstantReceiveTimestamp());
135138
assertNull(logEntry.getInsertId());
136139
assertNull(logEntry.getHttpRequest());
137140
assertNull(logEntry.getOperation());
@@ -147,7 +150,9 @@ public void testOf() {
147150
assertEquals(ImmutableMap.of(), logEntry.getLabels());
148151
assertEquals(ImmutableMap.of(), logEntry.getLabels());
149152
assertNull(logEntry.getTimestamp());
153+
assertNull(logEntry.getInstantTimestamp());
150154
assertNull(logEntry.getReceiveTimestamp());
155+
assertNull(logEntry.getInstantReceiveTimestamp());
151156
assertNull(logEntry.getInsertId());
152157
assertNull(logEntry.getHttpRequest());
153158
assertNull(logEntry.getOperation());
@@ -161,8 +166,10 @@ public void testOf() {
161166
public void testBuilder() {
162167
assertEquals(LOG_NAME, STRING_ENTRY.getLogName());
163168
assertEquals(RESOURCE, STRING_ENTRY.getResource());
164-
assertEquals(TIMESTAMP, (long) STRING_ENTRY.getTimestamp());
165-
assertEquals(RECEIVE_TIMESTAMP, (long) STRING_ENTRY.getReceiveTimestamp());
169+
assertEquals(TIMESTAMP.toEpochMilli(), (long) STRING_ENTRY.getTimestamp());
170+
assertEquals(TIMESTAMP, STRING_ENTRY.getInstantTimestamp());
171+
assertEquals(RECEIVE_TIMESTAMP.toEpochMilli(), (long) STRING_ENTRY.getReceiveTimestamp());
172+
assertEquals(RECEIVE_TIMESTAMP, STRING_ENTRY.getInstantReceiveTimestamp());
166173
assertEquals(SEVERITY, STRING_ENTRY.getSeverity());
167174
assertEquals(INSERT_ID, STRING_ENTRY.getInsertId());
168175
assertEquals(HTTP_REQUEST, STRING_ENTRY.getHttpRequest());
@@ -175,8 +182,10 @@ public void testBuilder() {
175182
assertEquals(STRING_PAYLOAD, STRING_ENTRY.getPayload());
176183
assertEquals(LOG_NAME, JSON_ENTRY.getLogName());
177184
assertEquals(RESOURCE, JSON_ENTRY.getResource());
178-
assertEquals(TIMESTAMP, (long) JSON_ENTRY.getTimestamp());
179-
assertEquals(RECEIVE_TIMESTAMP, (long) JSON_ENTRY.getReceiveTimestamp());
185+
assertEquals(TIMESTAMP.toEpochMilli(), (long) JSON_ENTRY.getTimestamp());
186+
assertEquals(TIMESTAMP, JSON_ENTRY.getInstantTimestamp());
187+
assertEquals(RECEIVE_TIMESTAMP.toEpochMilli(), (long) JSON_ENTRY.getReceiveTimestamp());
188+
assertEquals(RECEIVE_TIMESTAMP, JSON_ENTRY.getInstantReceiveTimestamp());
180189
assertEquals(SEVERITY, JSON_ENTRY.getSeverity());
181190
assertEquals(INSERT_ID, JSON_ENTRY.getInsertId());
182191
assertEquals(HTTP_REQUEST, JSON_ENTRY.getHttpRequest());
@@ -187,10 +196,13 @@ public void testBuilder() {
187196
assertEquals(TRACE_SAMPLED, JSON_ENTRY.getTraceSampled());
188197
assertEquals(SOURCE_LOCATION, JSON_ENTRY.getSourceLocation());
189198
assertEquals(JSON_PAYLOAD, JSON_ENTRY.getPayload());
199+
190200
assertEquals(LOG_NAME, PROTO_ENTRY.getLogName());
191201
assertEquals(RESOURCE, PROTO_ENTRY.getResource());
192-
assertEquals(TIMESTAMP, (long) PROTO_ENTRY.getTimestamp());
193-
assertEquals(RECEIVE_TIMESTAMP, (long) PROTO_ENTRY.getReceiveTimestamp());
202+
assertEquals(TIMESTAMP.toEpochMilli(), (long) PROTO_ENTRY.getTimestamp());
203+
assertEquals(TIMESTAMP, PROTO_ENTRY.getInstantTimestamp());
204+
assertEquals(RECEIVE_TIMESTAMP.toEpochMilli(), (long) PROTO_ENTRY.getReceiveTimestamp());
205+
assertEquals(RECEIVE_TIMESTAMP, PROTO_ENTRY.getInstantReceiveTimestamp());
194206
assertEquals(SEVERITY, PROTO_ENTRY.getSeverity());
195207
assertEquals(INSERT_ID, PROTO_ENTRY.getInsertId());
196208
assertEquals(HTTP_REQUEST, PROTO_ENTRY.getHttpRequest());
@@ -221,8 +233,10 @@ public void testBuilder() {
221233
.build();
222234
assertEquals(LOG_NAME, logEntry.getLogName());
223235
assertEquals(RESOURCE, logEntry.getResource());
224-
assertEquals(TIMESTAMP, (long) logEntry.getTimestamp());
225-
assertEquals(RECEIVE_TIMESTAMP, (long) logEntry.getReceiveTimestamp());
236+
assertEquals(TIMESTAMP.toEpochMilli(), (long) logEntry.getTimestamp());
237+
assertEquals(TIMESTAMP, logEntry.getInstantTimestamp());
238+
assertEquals(RECEIVE_TIMESTAMP.toEpochMilli(), (long) logEntry.getReceiveTimestamp());
239+
assertEquals(RECEIVE_TIMESTAMP, logEntry.getInstantReceiveTimestamp());
226240
assertEquals(SEVERITY, logEntry.getSeverity());
227241
assertEquals(INSERT_ID, logEntry.getInsertId());
228242
assertEquals(HTTP_REQUEST, logEntry.getHttpRequest());
@@ -314,7 +328,9 @@ private void compareLogEntry(LogEntry expected, LogEntry value) {
314328
assertEquals(expected.getLogName(), value.getLogName());
315329
assertEquals(expected.getResource(), value.getResource());
316330
assertEquals(expected.getTimestamp(), value.getTimestamp());
331+
assertEquals(expected.getInstantTimestamp(), value.getInstantTimestamp());
317332
assertEquals(expected.getReceiveTimestamp(), value.getReceiveTimestamp());
333+
assertEquals(expected.getInstantReceiveTimestamp(), value.getInstantReceiveTimestamp());
318334
assertEquals(expected.getSeverity(), value.getSeverity());
319335
assertEquals(expected.getInsertId(), value.getInsertId());
320336
assertEquals(expected.getHttpRequest(), value.getHttpRequest());

0 commit comments

Comments
 (0)