Skip to content

Commit

Permalink
Add Archive Trace button
Browse files Browse the repository at this point in the history
This button lets you easily reupload the current trace to a different
server.

The main motivation for having this is that you can have the
archival server have a very long retention period and use it as very
long term storage for traces that you care about. For example when
sharing a trace in a jira ticket since otherwise the link would expire
after 1 week.

Design doc explaining the reason behind this in more details and why we
went with this implementation: https://github.com/openzipkin/openzipkin.github.io/wiki/Favorite-trace
  • Loading branch information
drolando committed Mar 15, 2020
1 parent 94d7994 commit 6299661
Show file tree
Hide file tree
Showing 8 changed files with 174 additions and 1 deletion.
80 changes: 80 additions & 0 deletions zipkin-lens/src/components/TracePage/TraceSummaryHeader.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
/* eslint-disable no-alert */
import { t, Trans } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import PropTypes from 'prop-types';
Expand Down Expand Up @@ -105,6 +106,67 @@ const TraceSummaryHeader = React.memo(({ traceSummary, rootSpanIndex }) => {
? config.logsUrl.replace('{traceId}', traceSummary.traceId)
: undefined;

const archivePostUrl =
config.archivePostUrl && traceSummary ? config.archivePostUrl : undefined;

const archiveUrl =
config.archiveUrl && traceSummary
? config.archiveUrl.replace('{traceId}', traceSummary.traceId)
: undefined;

const archiveClick = useCallback(() => {
// We don't store the raw json in the browser yet, so we need to make an
// HTTP call to retrieve it again.
fetch(`${api.TRACE}/${traceSummary.traceId}`)
.then((response) => {
if (!response.ok) {
throw new Error('Failed to fetch trace from backend');
}
return response.json();
})
.then((json) => {
// Add zipkin.archived tag to root span
/* eslint-disable-next-line no-restricted-syntax */
for (const span of json) {
if ('parentId' in span === false) {
const tags = span.tags || {};
tags['zipkin.archived'] = 'true';
span.tags = tags;
break;
}
}

fetch(archivePostUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(json),
})
.then((response) => {
if (
!response.ok ||
(response.status !== 202 && response.status === 200)
) {
throw new Error('Failed to archive the trace');
}
if (archiveUrl) {
alert(
`Archive successful! This trace is now accessible at ${archiveUrl}`,
);
} else {
alert(`Archive successful!`);
}
})
.catch(() => {
alert('Failed to archive the trace');
});
})
.catch(() => {
alert('Failed to fetch trace from backend');
});
}, [archivePostUrl, archiveUrl, traceSummary]);

const handleSaveButtonClick = useCallback(() => {
if (!traceSummary || !traceSummary.traceId) {
return;
Expand Down Expand Up @@ -207,6 +269,24 @@ const TraceSummaryHeader = React.memo(({ traceSummary, rootSpanIndex }) => {
</Button>
</Grid>
)}
{archivePostUrl && (
<Grid item>
<Button
variant="outlined"
className={classes.actionButton}
target="_blank"
rel="noopener"
data-testid="archive-trace-link"
onClick={archiveClick}
>
<FontAwesomeIcon
icon={faFileAlt}
className={classes.actionButtonIcon}
/>
<Trans>Archive Trace</Trans>
</Button>
</Grid>
)}
</Grid>
</Grid>
</Box>
Expand Down
40 changes: 40 additions & 0 deletions zipkin-lens/src/components/TracePage/TraceSummaryHeader.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,44 @@ describe('<TraceSummaryHeader />', () => {
expect(logsLink.target).toEqual('_blank');
expect(logsLink.rel).toEqual('noopener');
});

it('does not render Archive Trace link with default config', () => {
const { queryByTestId } = render(
<TraceSummaryHeader
traceSummary={{
traceId: '1',
spans: [],
serviceNameAndSpanCounts: [],
duration: 1,
durationStr: '1μs',
rootSpan: {
serviceName: 'service-A',
spanName: 'span-A',
},
}}
/>,
);
expect(queryByTestId('archive-trace-link')).not.toBeInTheDocument();
});

it('does render Archive Trace link when logs URL in config', () => {
const { queryByTestId } = render(
<TraceSummaryHeader
traceSummary={{
traceId: '1',
spans: [],
serviceNameAndSpanCounts: [],
duration: 1,
durationStr: '1μs',
rootSpan: {
serviceName: 'service-A',
spanName: 'span-A',
},
}}
/>,
{ uiConfig: { archivePostUrl: 'http://localhost:9411/api/v2/spans' } },
);
const logsLink = queryByTestId('archive-trace-link');
expect(logsLink).toBeInTheDocument();
});
});
9 changes: 8 additions & 1 deletion zipkin-lens/src/zipkin/trace.js
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,12 @@ function addLayoutDetails(
}
}

export function detailedTraceSummary(root, logsUrl) {
export function detailedTraceSummary(
root,
logsUrl,
archivePostUrl,
archiveUrl,
) {
const serviceNameToCount = {};
let queue = root.queueRootMostSpans();
const modelview = {
Expand Down Expand Up @@ -381,6 +386,8 @@ export function detailedTraceSummary(root, logsUrl) {
modelview.duration = duration;
modelview.durationStr = mkDurationStr(duration);
if (logsUrl) modelview.logsUrl = logsUrl;
if (archivePostUrl) modelview.archivePostUrl = archivePostUrl;
if (archiveUrl) modelview.archiveUrl = archiveUrl;

return modelview;
}
2 changes: 2 additions & 0 deletions zipkin-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@ queryLimit | zipkin.ui.query-limit | Default limit for Find Traces. Defaults to
instrumented | zipkin.ui.instrumented | Which sites this Zipkin UI covers. Regex syntax. e.g. `http:\/\/example.com\/.*` Defaults to match all websites (`.*`).
logsUrl | zipkin.ui.logs-url | Logs query service url pattern. If specified, a button will appear on the trace page and will replace {traceId} in the url by the traceId. Not required.
supportUrl / zipkin.ui.support-url / A URL where a user can ask for support. If specified, a link will be placed in the side menu to this URL, for example a page to file support tickets. Not required.
archivePostUrl | zipkin.ui.archive-post-url | Url to POST the current trace in Zipkin v2 json format. e.g. 'https://longterm/api/v2/spans'. If specified, a button will appear on the trace page accordingly. Not required.
archiveUrl | zipkin.ui.archive-url | Url to a web application serving an archived trace, templated by '{traceId}'. e.g. https://longterm/zipkin/trace/{traceId}'. This is shown in a confirmation message after a trace is successfully POSTed to the `archivePostUrl`. Not required.
dependency.lowErrorRate | zipkin.ui.dependency.low-error-rate | The rate of error calls on a dependency link that turns it yellow. Defaults to 0.5 (50%) set to >1 to disable.
dependency.highErrorRate | zipkin.ui.dependency.high-error-rate | The rate of error calls on a dependency link that turns it red. Defaults to 0.75 (75%) set to >1 to disable.
basePath | zipkin.ui.basepath | path prefix placed into the <base> tag in the UI HTML; useful when running behind a reverse proxy. Default "/zipkin"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ static String writeConfig(ZipkinUiProperties ui) throws IOException {
generator.writeBooleanField("searchEnabled", ui.isSearchEnabled());
generator.writeStringField("logsUrl", ui.getLogsUrl());
generator.writeStringField("supportUrl", ui.getSupportUrl());
generator.writeStringField("archivePostUrl", ui.getArchivePostUrl());
generator.writeStringField("archiveUrl", ui.getArchiveUrl());
generator.writeObjectFieldStart("dependency");
generator.writeNumberField("lowErrorRate", ui.getDependency().getLowErrorRate());
generator.writeNumberField("highErrorRate", ui.getDependency().getHighErrorRate());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ class ZipkinUiProperties {
private String instrumented = ".*";
private String logsUrl = null;
private String supportUrl = null;
private String archivePostUrl = null;
private String archiveUrl = null;
private String basepath = DEFAULT_BASEPATH;
private boolean searchEnabled = true;
private Dependency dependency = new Dependency();
Expand Down Expand Up @@ -68,6 +70,15 @@ public String getLogsUrl() {
return logsUrl;
}

public String getArchivePostUrl() {
return archivePostUrl;
}


public String getArchiveUrl() {
return archiveUrl;
}

public void setLogsUrl(String logsUrl) {
if (!StringUtils.isEmpty(logsUrl)) {
this.logsUrl = logsUrl;
Expand All @@ -82,6 +93,19 @@ public void setSupportUrl(String supportUrl) {
if (!StringUtils.isEmpty(supportUrl)) {
this.supportUrl = supportUrl;
}

}

public void setArchivePostUrl(String archivePostUrl) {
if (!StringUtils.isEmpty(archivePostUrl)) {
this.archivePostUrl = archivePostUrl;
}
}

public void setArchiveUrl(String archiveUrl) {
if (!StringUtils.isEmpty(archiveUrl)) {
this.archiveUrl = archiveUrl;
}
}

public boolean isSearchEnabled() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ public class ITZipkinUiConfiguration {
+ " \"searchEnabled\" : true,\n"
+ " \"logsUrl\" : null,\n"
+ " \"supportUrl\" : null,\n"
+ " \"archivePostUrl\" : null,\n"
+ " \"archiveUrl\" : null,\n"
+ " \"dependency\" : {\n"
+ " \"lowErrorRate\" : 0.5,\n"
+ " \"highErrorRate\" : 0.75\n"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,22 @@ public void canOverrideProperty_logsUrl() {
assertThat(context.getBean(ZipkinUiProperties.class).getLogsUrl()).isEqualTo(url);
}

@Test
public void canOverrideProperty_archivePostUrl() {
final String url = "http://zipkin.archive.com/api/v2/spans";
context = createContextWithOverridenProperty("zipkin.ui.archive-post-url:" + url);

assertThat(context.getBean(ZipkinUiProperties.class).getArchivePostUrl()).isEqualTo(url);
}

@Test
public void canOverrideProperty_archiveUrl() {
final String url = "http://zipkin.archive.com/zipkin/traces/{traceId}";
context = createContextWithOverridenProperty("zipkin.ui.archive-url:" + url);

assertThat(context.getBean(ZipkinUiProperties.class).getArchiveUrl()).isEqualTo(url);
}

@Test
public void canOverrideProperty_supportUrl() {
final String url = "http://mycompany.com/file-a-bug";
Expand Down

0 comments on commit 6299661

Please sign in to comment.